1.3 COMPROBACIONES DE TIPOS EN EXPRESIONES
CONCEPTOS ESENCIALES En este escrito se manejaran algunos conceptos esenciales para la comprensión del tema como los siguientes: Vélez (s.f.) define símbolo como: “un elemento con nombre que el programador haya establecido como válido de él a través de una declaración.” (p. 5).
Un símbolo es un elemento que será válido para nuestro lenguaje y que tuvo que ser declarado en el mismo. Vélez (s.f.) explica que: “ El ejercicio de creación de un programa consiste en la declaración de una colección de símbolos con nombres que representan entidades semánticas utilizadas a lo largo del programa”. (p. 5). Se nos comenta que el hacer un programa implica declarar los símbolos que en el usaremos en conjunto con sus entidades, es decir, sus tipos. Vélez (s.f.) dice que: “Cada símbolo del lenguaje lleva asociado un tipo. El tipo de un
símbolo se utiliza para proporcionar información al compilador de cómo debe tratar semánticamente al mismo y qué operaciones debe permit ir realizar sobre él” . (p. 6). Se argumenta qué cada símbolo de nuestro programa lleva consigo un tipo, y que este le muestra al compilador como reconocerlo semánticamente y qué acciones puede llevar a cabo con él. Vélez (s.f.) rige que: “Un ámbito es un b loque acotado sintácticamente dentro del cual los símbolos allí declarados tienen vigencia.”. (p. 7).
Se define a un ámbito como un espacio delimitado en el cual se declaran símbolos que tienen una vigencia. Estos símbolos declarados en el ámbito tendrán uso mientras se encuentren dentro del mismo.
ANÁLISIS SEMÁNTICO
Vélez (s.f.) define que la fase de análisis semántico: “T iene por objetivo analizar los
componentes del árbol de análisis sintáctico que representan el programa con el fin de comprobar que se respetan ciertas reglas semánticas que dan un significado coherente a las construcciones del lenguaje. (p. 4).
El análisis semántico es uno de los tres principales análisis que se le hacen al código fuente antes de generar el código objeto necesario para nuestra máquina para poder realizar las tareas del programa. Este análisis comprueba que se cumplan con la serie de reglas que se define en el lenguaje. Vélez (s. f.) dice que: “El analizador semántico va comprobando la coherencia semántica del árbol de análisis sintáctico según se va construyendo”. (p. 4).
Nos menciona que el analizador semántico va verificando las reglas de la semántica conforme se va creando el árbol sintáctico. Aho, Sethi y Ullman (s. f.) mencionan que: “Un árbol sintáctico (abstracto) es una forma
condensada de un árbol de análisis sintáctico, útil para representar construcciones de lenguajes.” (p. 296).
Los autores del libro citado nos dicen que el árbol sintáctico nos es útil a la hora de crear las producciones del lenguaje a implementar, nos menciona que el árbol sintáctico es un “tipo” de árbol de análisis sintáctico. Aho, Sethi y Ullman (s. f.) argumentan que: “Un árbol de análisis sintáctico indica gráficamente como del símbolo inicial de una gramática deriva una cadena de l lenguaje.”.
(p. 29).
Se define que un árbol sintáctico es una forma gráfica de ir representando las producciones que se vayan generando; donde a partir de un símbolo inicial integrado en un nodo, se genera una cadena (del lenguaje definido) en sus nodos interiores. Aho, Sethi y Ullman (s. f.) definen que: “Un árbol sintáctico describe la estructura jerárquica natural de un programa fuente.” (p. 478).
Se dice que el árbol sintáctico describe jerarquía de la estructura del código fuente debido a que a partir de un símbolo inicial se van generando producciones de nuestro lenguaje Vélez (s. f.) concluye sobre el análisis semántico que: “Las responsabilidades son…
-Registrar declaraciones -Inferir tipos -Comprobar tipos -Comprobar corrección semántica”. (p. 4).
Por lo tanto, las funciones del análisis semántico son registrar declaraciones para después ir verificando la validez de expresiones; inferir tipos, se refiere a que debe ser capaz de definir distintos tipos de expresiones; comprobar tipos, en base a los patrones de expresiones debe el analizador semántico ser capaz de comprobar que sean correctos los tipos de las expresiones; y debe de comprobar que se haya realizado correctamente la corrección semántica. Entonces teniendo en cuenta para qué nos sirve el análisis semántico, podemos analizar una de sus fases indispensables para que este análisis sea efectivo, la cual es llamada comprobación de tipos.
COMPROBACIÓN DE TIPOS Vélez (s. f.) argumenta que: “La labor de comprobación de tipos consiste en conferir a las construcciones sintácticas del lenguaje la semántica de tipificación que acabamos de escribir y en realizar todo tipo de comprobaciones de tipo de dicha índole.”. (p. 9).
En otras palabras, la labor de la comprobación de tipos se hace mientras se van construyendo las producciones sintácticas, al ir asignando un tipo a cada una de estas, y en base al lenguaje definido realizar las verificaciones de todos los tipos establecidos. Esta fase de comprobación se encuentra entre la fase del análisis semántico y la fase de generación de código intermedio. Cabe mencionar que hay dos tipos de comprobaciones de tipos, las cuales son las comprobaciones estáticas y las comprobaciones dinámicas. Vélez (s. f.) argumenta que : “Las comprobaciones estáticas recogen el compendio de todas aquellas tareas de carácter semántico fue, por su naturaleza, pueden ser realizadas directamente durante la fase de compilación mediante el uso de los artefactos y mecanismos pr opios de dicha fase.”. (p. 9). Este tipo de comprobación se caracteriza porque se realiza durante la fase de compilación usando los métodos conocidos específicamente para esa fase. Vélez (s. f.) también define que las comprobaciones dinámicas son: “ aquellas que no se realizan durante la fase de compilación y se delegan al momento de la ejecución del programa .”. (p. 9). Contrario a la comprobación estática, la comprobación dinámica no se hace en el momento de la compilación del programa, sino al momento de ejecutarlo. Vélez (s. f.) continúa diciendo que: “Ello requiere generar código ejecutable específicamente diseñado para realizar tales comprobaciones.”. (p. 9).
En la comprobación dinámica se necesita crear un código especial para realizar comprobaciones de tipos, lo cual nos puede traer más desventajas a la hora de realizar las comprobaciones ya que los lenguajes más grandes generan programas más largos, lentos e incluso inseguros. Dentro de las comprobaciones semánticas estáticas podemos encontrar tres tipos, que son la gestión de declaraciones, la verificación de tipos y la inferencia de tipos. Sobre la gestión de declaraciones Vélez (s.f.) dice que: “se encarga de registrar todas las declaraciones realizadas por el programador a lo largo de distintos ámb itos.”. (p. 10). Se había mencionado que un ámbito era un bloque en el que se declaraban algunos tipos, la tarea de la gestión de declaraciones es de verificar que no se produzcan errores por la duplicación de nombres con identificadores de otras declaraciones. Ahora hablando de la verificación de tipos Vélez (s.f.) argumenta que: “comprueba la compatibilidad de tipos de todas las expresiones del código fuente recuperando la información durante la gestión de declaraciones. Además se asegura que no existe en el programa ninguna referencia a ningún símbolo no declarado.”. (p. 10).
Sobre la fase de verificación de tipos se dice que esta se encarga de analizar todo el código fuente, buscando que cada declaración corresponda a un tipo válido en el lenguaje, también se encarga de cerciorarse que no haya uso de símbolos no declarados en el programa. Aho, Lam, Sethi y Ullman (2008) mencionan que: “ Para realizar la comprobación de tipos,
un compilador debe asignar una expresión de tipos a cada componente del programa fuente”. (p. 386).
Podemos ver que para comprobar los tipos de datos de un programa fuente el compilador debe de establecer una expresión de tipo para cada parte del programa. Esto quiere decir que se debe definir un tipo de expresión que sea válida por las reglas de nuestro lenguaje y así el compilador al detectar una expresión de un código fuente sea capaz de reconocerla y verificar si es correctamente expresada, es decir, que tiene la coherencia necesaria para poder ejecutarla. Aho, Lam, Sethi y Ullman (2008) también argumentan que: “Después, el compilador debe determinar que estas expresiones de tipos se conforman a una colección de reglas lógicas, conocida como el sistema de tipos para el lenguaje fuente”. (p. 386).
Se dice que el compilador crea un sistema de tipos a partir de los tipos de expresiones generadas. Enseguida se menciona que el compilador debe establecer que las expresiones de tipos creadas se hicieron a partir del sistema de tipos, que es un conjunto de reglas que se obtienen a partir del lenguaje fuente. Estas reglas permiten asignar tipos a las distintas partes de un programa y también nos permite verificar su corrección. Vélez (s. f.) determina que: “el sistema de tipos de un lenguaje es una especificación de alto nivel que describe de forma precisa el conjunto de reglas y restricciones semánticas de tipificación que se aplican sobre las construcciones sintácticas del lenguaje.”. (p. 1 1). El sistema de tipos contiene todas las reglas y restricciones que se aplicarán al código fuente para verificar que es parte del lenguaje establecido. Aho, Sethi y Ullman (2006) aclaran que: “El diseño de un comprobador de tipos para un lenguaje se basa en información acerca de las construcciones sintácticas del lenguaje, la noción de tipos y las reglas para asignar tipos a las construcciones del lenguaje.”. (p.
356). Se nos dice que para nosotros poder realizar la función de comprobación de tipos de expresiones necesitamos saber sobres la sintaxis del lenguaje, los tipos que se vayan a manejar y las reglas que se deben cumplir para el lenguaje.
Aho, Lam, Sethi y Ullman (2008) comparten que: “La comprobación de tipos tiene el potencial de atrapar errores en los programas. En principio, cualquier comprobación puede realizarse en forma dinámica, si el código de destino lleva el tipo de un elemento.” (pp. 386 – 387).
Anteriormente se mencionó que para la comprobación dinámica es necesario generar un código especial, entonces este código se puede generar de manera que sea capaz de identificar errores e incluso que hacer con ellos. Aho, Lam, Sethi y Ullman (2008) dicen que: “Un sistema de tipos elimina la necesidad de la comprobación dinámica para los errores de tipos, ya que nos permite determinar una forma estática que estos errores no pueden ocurrir cuando se ejecuta el programa de destino.” (p. 387)
Esto se debe a que el sistema de tipos incluye todas las reglas, lo cual nos sirve al momento de realizar la comprobación de manera estática. Aho, Lam, Sethi y Ullman (2008) señalan que: “Una implementación de un lenguaje está fuertemente tipificada si un compilador garantiza que los programas que acepta se ejecutarán sin errores.” (p. 38 7) Esto quiere decir que un lenguaje fuertemente tipificado es mucho más seguro en cuanto a la generación de errores, ya que nos asegura que en el momento de la ejecución no se producirán errores. Aho, Lam, Sethi y Ullman (2008) agregan: “ Además de su uso para la compilación, las ideas de la comprobación de tipos se han utilizado para mejorar la seguridad de los sistemas que permiten la importación y ejecución de módulos de software” (p.38 7). Existen aplicaciones con el uso de comprobación de tipos más allá de la compilación de programas, como los que se mencionan arriba. Veamos la comprobación de tipos en las expresiones: Aho, Sethi y Ullman (2006) muestran lo siguiente: “Las siguientes reglas semánticas señalan que las constantes representadas por los componentes léxicos literal y núm tienen tipo char e integer , respectivamente: E→literal
{E.tipo:= char }
E→núm
{E.tipo:= integer } “. (p. 362).
Se nos demuestra que a cada expresión se le asigna un tipo, uno del tipo char y otro del tipo integer, y este tipo es un atributo de la expresión, el cual es asignado por el sistema de tipos.
Continuando con las expresiones, Aho, Sethi y Ullman (2006) dicen que: “Se utiliza la función busca(e) para traer el tipo guardado en la entrada de la tabla de símbolos apuntada por e.”. (p. 362). Lo siguiente que se hace es buscar la referencia en la tabla de símbolos para traer el tipo de la expresión. Aho, Sethi y Ullman (2006) señalan que: “Cuando un identificador aparece en una expresión, se trae su tipo declarado y se asigna al atributo tipo: E→id
{E.tipo:= busca( id.entrada) } “. (p. 362).
En el caso de que aparezca un identificador en la expresión se trae el tipo que se haya declarado para asignárselo al su atributo llamado tipo. Aho, Sethi y Ullman (2006) indican: “La expresión formada aplicando el operador mod a dos subexpresiones de tipo integer tiene tipo integer, de lo contrario, su tipo es error_tipo.
La regla es then integer else E→E 1 mod E 2 {E.tipo:= if E 1.tipo=integer and E 2.tipo=integer error_tipo} “. (p.362).
Se nos muestra una regla aplicada a dos subexpresiones a las que se les aplica el operador mod, en la que dice que si el tipo de cada una de estas expresiones es de tipo integer, entonces el resultado que obtengamos será de tipo integer, y en caso de que no fuera así tendríamos un error de tipo.
SISTEMA DE TIPOS Ya se mencionó de forma general que el sistema de tipos contiene todas las reglas que se deben cumplir para generar las producciones, incluye los tipos que son validos para el lenguaje a implementar. Analicemos algunos de los elementos del sistema de tipos. Vélez (s. f.) comenta que: “Para definir el sistema de tipos de un lenguaje de manera formal, sistemática e independientemente de la sintaxis propia del mismo se utilizan expresiones de tipos. Cada tipo primitivo tiene una expresión de tipo asociada. ”. (p. 12). Se dice que para generar un sistema de tipos que no dependa de su sintaxis, se utilizan expresiones de tipos. Vélez (s. f.) define: “Una expresión de tipos es un mecanismo formal utilizado por los sistemas de tipos para representar el tipo de una construcción sintáctica propia del lenguaje.”. (p. 12).
Nos dice que una expresión de tipos es una herramienta para representar las producciones sintácticas del lenguaje. También se expone otro término utilizado para el sistema de tipos que es el tipo primitivo. Vélez (s. f.) expone: “Los tipos primitivos de un lenguaje determinan la colección de tipos de datos originales que el lenguaje pone a disposición del programador para componer estructuras de datos más complejas.”. (p. 13).
Entonces, los tipos primitivos son los tipos de datos que nos sirven para realizar otros tipos de datos más abstractos, el programador hace uso de ellos al crear tipos nuevos. Vélez (s. f.) nos menciona: “ La variedad de tipos primitivas es una característica propia del lenguaje, pero en general se distinguen cuatro categorías: ordinales, reales, lógicos y de caracter. (p.13). Nos dice que los tipos primitivos pueden variar de lenguaje a lenguaje pero usualmente en la mayoría se usan los de tipos ordinales, reales, lógicos y carácter como los principales de cada compilador. Otro elemento es un constructor de tipos, Vélez (s. f.) nos menciona que: “Los constructores de tipos son mecanismos sintácticos del lenguaje que permiten combinar otras construcciones de tipos, primitivos o compuestos, para generar estructuras más complejas.”. (p. 13). Este elemento es una herramienta que nos sirve para crear otros tipos a partir de tipos ya declarados, sean primitivos o compuestos. Los tipos generados por estos constructores de tipos son llamados tipos compuestos o tipos complejos.
COMPROBADOR DE TIPOS Veamos que es un comprobador de tipos y los elementos que lo componen. Vélez (s. f.) define: “Un comprobador de tipos es la parte del compilador que se encarga de implementar el sistema de tipos del lenguaje a través de mecanismos de traducción dirigida por la sintaxis.”. (p. 25).
El comprobador de tipos es el elemento que se encarga de usar el sistema de tipos para verificar la correcta semántica del programa fuente, usando la traducción dirigida por la sintaxis. Un breve ejemplo: expresion ::= expresion:e1 MAS expresion:e2 {: t1 = <>
t2 = <>
<> No: <> si: <>:} Vélez (s. f.) mencionan que: “ En el caso de las expresiones es necesario recuperar el tipo de cada subexpresión y comprobar su compatibilidad con respecto al operador que las combina. Si no son compatibles se emite un mensaje de error semántico .”. (p. 25). Si dos expresiones no son compatibles con una operación porque su tipo es diferente, entonces el comprobador de tipos manda un mensaje de error diciendo que son expresiones de tipo diferente. Vélez (s. f.) articula: “El gestor de errores proporciona métodos para informar de errores recuperables y no recuperables así como para trazar la ejecución del comprobador semántico.”. (p. 31). El gestor de errores es otro elemento que avisa cuando existe un error semántico durante la compilación, selecciona el método para emitir el mensaje de error y el tipo de error, así como definir una ruta de ejecución del comprobador semántico. Por ejemplo: exp ::= exp:e1 MAS exp:e2 {: TypeIF t1 = <> TypeIF t2 = <> semanticErrorManager.semanticDebug (“Tipo de e1:” + t1); semanticErrorManager.semanticDebug (“Tipo de e2:” + t2);
if (t1.isCompatible (t2, TypeIF.MAS))
{...} else semanticErrorManager.semanticFatalError (“tipos incompatibles”);:}
Y su Administrador de errores: SemanticErrorManager + void semantiDebug (String message) + void semanticInfo (String message) + void semanticWarn (String message) + void semanticError (String message) + void semanticFatal (String message)
Vélez (s. f.) define que: “ Una tabla de tipos es una estructura de datos de tabla hash donde se registran todas las declaraciones de tipos realizadas por el usuario programador en un determinado ámbito .”. (p. 32). La tabla de tipos contiene todos los tipos creados por el programador, dependiendo del bloque en donde se utilice.
REGLAS PARA LA COMPROBACION DE TIPOS Aho, Lam, Sethi y Ullman (2008) argumentan que: “La comprobación de tipos puede tomar dos formas: síntesis e inferencia. La síntesis de tipos construye el tipo de una expresión a partir de los tipos de sus subexpresiones. Requieren que se declaren los nombres antes de utilizarlos.” (p. 387). Se menciona que la comprobación de tipos tiene dos maneras que son síntesis e inferencia y nos dice que la forma de síntesis es aquella que determina el tipo en base a las subexpresiones que conforman la expresión. Aho, Lam, Sethi y Ullman (2008) nos muestran: “El tipo de E1 + E2 se define en términos de los tipos de E 1 y E2. Una regla común para la síntesis de tipos tiene la siguiente forma: if f tiene el tipo s → t and x tiene el tipo s, then la expresión f(x) tiene el tipo t ”. (p. 387).
La forma de síntesis es aquella en la que el tipo se obtiene a partir de las subexpresiones, entonces se nos ejemplifica en donde si un símbolo tiene el tipo “s”, y otro símbolo tiene el tipo “s” y se realiza una operación con ellos, entonces el resultado tiene que ser del tipo “s”. Aho, Lam, Sethi y Ullman (2008) “Aquí, f y x denotan expresiones, y s→t denota una función de s a t . Esta regla para las funciones con un argumento se pasa a las funciones con varios argumentos.” (p. 387).
En ese ejemplo, f y x son expresiones, en donde f tiene el tipo de s a t, y como la expresión x tiene el tipo s, entonces también es válida la función s a t. Aho, Lam, Sethi y Ullman (2008) “La inferencia de tipos determina el tipo de una construcción del lenguaje a partir de l a forma en que se utiliza.” . (p. 397).
La inferencia es la otra manera de comprobación de tipos, y se dice que esta determina el tipo de expresión en base a cómo se está utilizando la expresión. Aho, Lam, Sethi y Ullman (2008) “Las variables que represent an expresiones de tipos nos
permiten hablar acerca de los tipos desconocidos. Se pueden usar las letras griegas para variables de los tipos en las expresiones de tipos. Aho, Lam, Sethi y Ullman (2008) “Una regla común para la inferencia de tipos tiene la
siguiente forma: If f(x) es una expresión, then para cierta α y β, f tiene el tipo α → β and x tiene el tipo α.”. (p. 387).
En esta otra regla que se menciona, pero del tipo de inferencia, se nos dice que si tenemos una expresión con determinados argumentos y uno de ellos es de determinado tipo, y la expresión se traslada a otro tipo, entonces sus argumentos también se trasladan a ese tipo. Ejemplo 6 1 La codificación de las expresiones de tipos de este ejemplo proviene de un compilador de C escrito por D. M. Ritchie. También la utiliza el compilador de C descrito en Johnson 11979]. Considérense expresiones de tipos con los siguientes constructores de tipos para apuntadores, funciones y matrices: pointer (t) indica un apuntador al tipo 1.freturns (0 señala una función de algunos argumentos que devuelve un objeto de tipo 1. y array (1) indica una matriz (de longitud no determinada) de elementos de tipo t. Obsérvese que se han simplificado los constructores de tipos matriz y función. Se tendrá localizado el número de elementos de una matriz, pero el número se guarda en otro lugar, de modo que no forme parte del constructor de tipo array. Asimismo, el único operando del constructor freturns es el tipo del resultado de una función: los tipos de los argumentos de la función se guardarán en otro lugar. Por tanto, los objetos con expresiones estructuralmente equivalentes de este sistema de tipos quizá sigan sin pasar la prueba de la figura 6.6 que ahí se aplica a un sistema de tipos más detallado. Como cada uno de estos constructores es un operador unario, las expresiones de tipos formadas mediante la aplicación de dichos constructores a tipos básicos tienen una estructura muy uniforme. Ejemplos de dichas expresiones de tipos son: char freturns (citar) pointer (freeturns (char)) array (pointer (freturns (char))) Cada una de las expresiones anteriores puede representarse con una secuencia de bits mediante un esquema de codificación simple. Como sólo hay tres constructores de tipos, se pueden utilizar dos bits para codificar un constructor, de la siguiente manera: CONSTRUCTOR DE TIPOS CODIFICACIÓN Pointer
01
array
10
freturns
11
Los tipos básicos de C se codifican utilizando cuatro bits en Johnson [1979]; los cuatro tipos básicos de este ejemplo se pueden codificar de la siguiente manera: TIPO BÁSICO CODIFICACIÓN boolean
0000
char integer real
0001 0010 0011
Las expresiones de tipos limitadas ya se pueden codificar como secuencias de bits. Los cuatro bits situados más a la derecha codifican el tipo básico de una expresión de tipo. Moviéndose de derecha a izquierda, los dos bits siguientes indican el constructor aplicado al tipo básico, los dos bits siguientes describen el constructor que se aplica a éste, y así sucesivamente. Por ejemplo. EXPRESIÓN DE TIPO
CODIFICACIÓN
char
000000
0001
freturns (char)
000011
0001
pointer (freturns (char))
000111
0001
array (pointer (freturns (char)))
100111
0001
CONVERSIONES DE TIPOS
Aho, Lam, Sethi y Ullman (2008) mencionan que: “La representación de enteros y números de punto flotante es distinta dentro de una computadora”. (p. 388 ). Se nos dice que en una computadora se representa de manera diferente un tipo de dato entero que un tipo de dato con punto flotante. Aho, Lam, Sethi y Ullman (2008) también argumentan que: “Tal vez el compilador tenga que convertir uno de los operandos .” (p. 388) Aquí se nos explica que al realizar una operación probablemente un operando tenga que ser convertido a otro tipo, eso podría suceder cuando se manejen dos tipos de datos diferentes. Aho, Lam, Sethi y Ullman (2008) ejemplifican: “Considere las expresiones como x + i, en donde x es de tipo flotante, e i es de tipo entero. Suponga que los enteros se convierten a números de punto fl otante cuando es necesario, usando un operador unario (float).”. (p. 388). Tenemos un ejemplo de una operación que es la suma de dos valores, donde un operando es del tipo flotante y otro es del tipo entero, y se convierte el valor de tipo entero a valor de tipo flotante mediante el operador unario float (es un operador unario ya que solo requiere un parámetro para poder operar sobre él) para poder realizar esa operación íntegramente. Aho, Lam, Sethi y Ullman (2008) explican lo siguiente: “Presentaremos ot ro atributo E.tipo, cuyo valor es integer o float. La regla asociada con E → E 1 + E 2 se basa en el siguiente seudocódigo: if
) E.tipo=integer; ( E 1.tipo=integer and E 2.tipo=integer
else if ( E 1.tipo=float and E 2.tipo=integer )”. (p. 388).
Ahora se muestra un ejemplo donde se presentan dos expresiones, en donde si dos expresiones son del mismo tipo, al realizarle una operación dará como resultado otra expresión del mismo tipo. Aho, Lam, Sethi y Ullman (2008) “A medida que se incrementa el número de tipos suj etos
a conversión, el número de casos aumenta con rapidez. Por lo tanto, con números extensos de tipos es importante una organización cuidadosa de las acciones semánticas.”
(p. 388). También nos mencionan que es de suma importancia el tener una organización de la semántica porque al tener muchos tipos entonces también se tendrán muchos casos de conversión.
Existen la conversión implícita y la conversión explicita. La cual se diferencia por su modo de operación. Aho, Lam, Sethi y Ullman (2008) e xplican: “Se dice que la conversión de un tipo a otro es implícita si el compilador la realiza en forma automática.”. (p. 389).
Un tipo de conversión es la conversión implícita en donde el compilador es el que actúa automáticamente para realizar la conversión al detectar un caso en donde sea necesario la conversión para validar la expresión. Aho, Lam, Sethi y Ullman (2008) añaden: “Las conversiones del tipo implícitas, también conocidas como coerciones, están limitadas en muchos lenguajes de programación a las conversiones de ampliación.”. (p. 389).
Las conversiones implícitas también se conocen como coerciones, y que normalmente son conversiones de ampliación, es decir, que el tipo de datos se va convirtiendo jerárquicamente al tipo de datos siguiente si es que es posible realizar la conversión. Aho, Lam, Sethi y Ullman (2008) agregan: “ Se dice que la conversión es explícita si el
programador debe escribir algo para provocar la conversión. A las conversiones explícitas también se les conoce como conversione s o casts.”. (p. 389). Para que una conversión sea explicita se debe escribir por el programador especificando el tipo de conversión que quiere realizar. Ejemplo: Conversión implícita con operadores aritméticos Exp → num _entero
{Exp.tipo = integer }
Exp → num_real
{Exp.tipo = real }
Exp → id
{Exp.tipo = TDS_obtenerTipo(id.texto)}
Exp → Exp o p Exp
{Exp0.tipo = if (Exp1.tipo=integer) and (Exp2.tipo=integer) then return (integer) else if (Exp1.tipo=integer) and (Exp2.tipo=real) then convertirReal(Exp1) return(real) else if (Exp1.tipo=real) and (Exp2.tipo=integer) then convertirReal(Exp2) return(real)
else if (Exp1.tipo=real) and (Exp2.tipo=real) then return(real) else return(error) } Siendo op un operador aritm´etico: +,−, , /. ∗
RECUPERACION DE ERRORES Aho, Sethi y Ullman (2006) analizan lo siguiente: “Como la comprobación de tipos tiene la
capacidad de descubrir errores en los programas, es importante que un comprobador de tipos haga algo razonable cuando se descubre un error.¨. (p. 360). Y es que recordemos que la comprobación busca si una declaración está permitida según nuestro lenguaje definido, entonces, es de importancia que la comprobación de tipos sea capaz de no solamente detectarlos errores de tipos, sino también que haga al respecto. Aho, Sethi y Ullman (2006) dicen que: “El compilador debe informar de la naturaleza y la
posición del error. Es mejor que el comprobador de tipos se recupere de los errores, para que pueda comprobar el resto de la entrada.” (p. 360).
Entonces se nos dice que un compilador nos dice de qué tipo de error se trata y donde se encuentra este, más sin embargo, el comprobador de tipos debe ser capaz de recuperarse de este error para poder seguir analizando el resto del programa. Aho, Sethi y Ullman (2006) indican: “Como el manejo de errores afecta a las reglas de comprobación de tipos, tiene que diseñarse como parte del sistemas de tipos desde el principio; las reglas tienen que servir para los errores.”. (p. 360) . Al generar el sistema de tipos es conveniente manejar las reglas de maneras que sean capaces de tratar los errores que se puedan presentar. Aho, Sethi y Ullman (2006) mencionan que: “La inclusión del manejo de errores puede
dar como resultado un sistema de tipos que vaya más allá del necesario para especificar programas correctos. Por ejemplo, cuando se ha producido un error, es posible que se desconozca el tipo de fragmento de programa formado de manera incorrecta.”. (p. 360).
Por último acerca de la recuperación de errores, se nos menciona que al incluir reglas que nos ayuden a manejar los errores que se pudieran presentar a la hora de realizar la comprobación de tipos, tendríamos no solo el resultado principal de la comprobación de tipos, que es mostrarnos si un programa está correctamente especificado, sino que también tendríamos la oportunidad de detectar en que parte de nuestro código fuente tenemos errores para poder tratarlos y corregirlos.
BIBLIOGRAFÍA: Aho, A. V., Lam, M. S., Sethi, R. y Ullman, J. D. (2008). Compiladores. Principios, Técnicas y Herramientas. Pearson Education Inc. Mexico. Aho, A. V., Sethi, R. y Ullman, J. D. (2006). Compiladores. Principios, técnicas y Herramientas. Dragon Book. Joyanes, L., Sánchez, L. y Zahonero, I. (2007). Estructura de datos en C++. McGrawHill/Interamericana de España. España. Vélez J. (s. f.). Análisis semántico II. Comprobación de tipos. [Versión electrónica]. En: UNED. España.