Sistema de Tipos Semantica

download Sistema de Tipos Semantica

of 42

Transcript of Sistema de Tipos Semantica

Sistema de tipos y SemnticaIshmael: seguro que todo esto tiene un significado. Hermn Melville, Moby Dick

CONTENIDO DEL CAPTULO

3.1. Sistemas de tipos. 3.2. Dominios de semntica y transformacin de estado. 3.3. Semntica operacional. 3.4. Semntica axiomtica. 3.5. Semntica denotativa. 3.6. Ejemplo: semntica de asignaciones y expresiones de J-PL0.

Los sistemas de tipos se han convertido en algo muy importante en el diseo de lenguajes porque podemos utilizarlos para formalizar la definicin de los tipos de datos de un lenguaje y su utilizacin correcta en los programas. Con frecuencia asociamos los sistemas de tipos con la sintaxis, especialmente en lenguajes en cuyos programas se revisan los tipos en tiempo de compilacin. Para estos lenguajes, un sistema de tipos es una extensin de definiciones que impone restricciones sintcticas especficas (como por ejemplo, el requisito de tener que declarar todas las variables a las que se haga referencia en un programa) que no podemos expresar en BNF o EBNF. Para los lenguajes en cuyos programas la revisin de tipos se realiza en tiempo de ejecucin, podemos ver un sistema de tipos como parte de la semntica del lenguaje. Por tanto, el sistema de tipos de un lenguaje se encuentra entre la sintaxis y la semntica y podemos visualizarlo apropiadamente en cualquiera de los dos campos. La definicin de un lenguaje de programacin est completa slo cuando estn completamente definidos su semntica, su sintaxis y su sistema de tipos. La semntica de un lenguaje de programacin es una definicin del significado de cualquier programa que sea sintcticamente vlido desde los puntos de vista de la sintaxis concreta y de la revisin de tipos esttica1. Podemos definir el significado de un programa de varias maneras diferentes. Una idea intuitiva sencilla del significado de un programa es lo que sucede en una computadora (real o de ejemplo) cuando ejecutamos el programa. Una descripcin precisa de esta idea es la llamada semnticaNo hace demasiado tiempo, era posible escribir un programa sintcticamente correcto en un lenguaje en particular que se comportaba de manera diferente cuando lo ejecutbamos en plataformas diferentes (con la misma entrada). Esta situacin surgi porque la definicin de la semntica del lenguaje no era lo suficientemente precisa como para exigir que todos sus compiladores tradujeran un programa a versiones de lenguaje lgicamente equivalentes. En estos ltimos aos, los diseadores de lenguajes se han dado cuenta de que el trato formal de la semntica es tan importante como el trato formal de la sintaxis para asegurarse de que un programa en particular significa lo mismo sin importar la plataforma en la que lo ejecutemos. Los lenguajes modernos son mucho mejores a este respecto que los ms antiguos.1

operacional2. Otro modo de ver el significado de un programa es empezar con una especificacinformal de lo que se supone que tiene que hacer el programa y despus demostrar rigurosamente que lo hace, utilizando una serie sistemtica de pasos lgicos. Este mtodo evoca la idea de semntica

axiomtica. Un tercer modo de ver la semntica de un lenguaje de programacin es definir elsignificado de cada tipo de instruccin que se produce en la sintaxis (abstracta) como una funcin matemtica de transformacin de estado. As, podemos explicar el significado de un programa como una coleccin de funciones que operan en el estado del programa. Este mtodo se llama semntica

denotativa.Los tres mtodos de definicin semntica tienen ventajas e inconvenientes. La semntica operacional tiene la ventaja de representar el significado del programa directamente en el cdigo de una mquina real (o simulada). Pero esto es tambin una debilidad potencial, debido a que la definicin de la semntica de un lenguaje de programacin con base a una arquitectura en particular, ya sea real o abstracta, restringe la utilidad de esa definicin a los escritores-compiladores y programadores que trabajen con arquitecturas diferentes. Adems, el equipo virtual en el que ejecutamos las instrucciones tambin necesita una descripcin semntica, lo que aade complejidad y puede llevarnos a definiciones viciadas. La semntica axiomtica es particularmente til en la exploracin de las propiedades formales de los programas. A los programadores que deben escribir programas correctos a partir de un conjunto de especificaciones preciso, les resulta particularmente til este estilo semntico. La semntica denotativa es valiosa porque su estilo funcional lleva la definicin semntica de un lenguaje a un nivel alto de precisin matemtica. A travs de ella, los diseadores de lenguajes obtienen una definicin funcional del significado de la elaboracin de cada lenguaje que es independiente de cualquier arquitectura de equipo en particular. En este captulo, introducimos un enfoque formal de la definicin del sistema de tipos de un lenguaje. Tambin introducimos los tres modelos de definicin semntica, prestando especial atencin al modelo denotativo. Utilizamos la semntica denotativa en captulos posteriores para estudiar distintos conceptos en el diseo de un lenguaje y para aclarar diversos problemas semnticos. Este modelo es particularmente valioso, porque tambin nos permite explorar activamente estos conceptos del diseo de un lenguaje en un entorno de laboratorio.

Tcnicamente, existen dos tipos de semntica operacional, llamadas semntica operacional (a veces semntica natural) tradicional y estructurada. En este captulo, hablaremos de esta ltima.

2

3.1.

SISTEMAS DE TIPOS

Un tipo es un conjunto bien definido de valores y de operaciones en esos valores. Por ejemplo, el tipo familiar int tiene valores {..., -2, -1, 0, 1, 2, ...} y operaciones {+, -, *,/,...} en esos valores. El tipo boolean tiene valores {verdadero, falso) y operaciones {&&, || y !} en esos valores. Un sistema de tipos es un sistema bien definido para asociar tipos con variables y otros objetos definidos en un programa. Algunos lenguajes, como C y Java, asocian un nico tipo con una variable a lo largo de la vida de esa variable en tiempo de ejecucin, y podemos determinar los tipos de todas las variables en tiempo de compilacin. Otros lenguajes, como Lizp y Scheme, permiten que el tipo de una variable, as como su valor, cambien mientras el programa se ejecuta. Los lenguajes de la primera clase se llaman lenguajes de tipos estticos, mientras que los otros son de tipos dinmicos. Un lenguaje de tipos estticos permite que las reglas de su tipo se definan completamente con base a su sintaxis abstracta. A menudo llamamos a esta definicin semntica esttica y nos ofrece un medio de aadir informacin sensible al contexto a un analizador que la gramtica BNF no puede proporcionarnos. Identificamos este proceso como anlisis semntico en el Captulo 2 y lo estudiaremos con ms atencin ms adelante. Un error de tipo es un error en tiempo de ejecucin que se produce cuando intentamos una operacin en un valor para el que no est bien definida. Por ejemplo, tomemos la expresin de Cx+u.p, donde u est definida como la unin {int a; double p;} e iniciada por la asignacin: u.a = 1;

Esta expresin puede provocar un error de tipo, debido a que los valores alternativos int y double de u comparten la misma direccin de memoria y u se inicia con un valor int. Si x es un double, la expresin x+u. p provoca un error que no podemos comprobar ni en tiempo de compilacin ni en tiempo de ejecucin. Un lenguaje de programacin es de tipos estrictos si su sistema de tipos permite la deteccin de todos los errores de tipo de los programas, ya sea en tiempo de compilacin como en tiempo de ejecucin, antes de que la instruccin en la que se pueden producir se ejecute. (El hecho de que un lenguaje tenga tipos estticos o dinmicos no evita que tenga tipos estrictos.) Por ejemplo, Java es un lenguaje con tipos estrictos, mientras que C no lo es. Es decir, en C podemos escribir y ejecutar la expresin x+1 sin importar si el valor de x es un nmero o no. Generalmente, los tipos estrictos fomentan programas ms fiables y se consideran como una virtud en el diseo de lenguajes de programacin. Un programa es de tipo seguro si sabemos que no tiene errores de tipo. Por definicin, todos los programas de un lenguaje con tipos estrictos son de tipo seguro. Adems, un lenguaje es de tipo

seguro si todos sus programas lo son. Los lenguajes con tipos dinmicos como Lisp deben ser, necesariamente, de tipo seguro. Como todas sus comprobaciones de tipos se realizan en tiempo de ejecucin, los tipos de las variables pueden cambiar dinmicamente. Esto supone cierto coste adicional en el rendimiento en tiempo de ejecucin de un programa, debido a que el cdigo de comprobacin de tipos ejecutable debe estar entremezclado con el cdigo escrito por el programador. Cmo podemos definir un sistema de tipos para un lenguaje de manera que podamos detectar los errores de tipo? Respondemos a esta pregunta en la seccin siguiente.

3.1.1.

Cmo formalizar el sistema de tipos

Una manera de definir el sistema de tipos de un lenguaje es escribir un conjunto de especificaciones de funciones que defina lo que significa para un programa ser de tipo seguro3. Podemos escribir estas reglas como funciones de valor boolean y podemos expresar ideas como todas las variables declaradas tienen nombres nicos o todas las variables utilizadas en el programa deben declararse. Como podemos expresar funcionalmente las reglas para escribir programas de tipo seguro, podemos implementarlas en Java como un conjunto de mtodos de valor boolean. La base de esta definicin funcional es un mapa de tipos, que es un conjunto de parejas que representa las variables declaradas y sus tipos. Podemos escribir el mapa de tipos, tm, de un programa as:

tm = {v1, t1, v2, t2, ... , vn, tn}donde cada vi indica una Variable y cada ti indica su Tipo declarado. Para el lenguaje J-PL0, las

Variables de un mapa de tipos son nicas entre s y cada tipo se toma de un conjunto de tiposdisponibles que se fijan en tiempo de compilacin. En J-PL0, ese conjunto es fijo permanentemente (es {int, boolean}). En la mayora de los lenguajes, como C y Java, el programador puede ampliar ese conjunto definiendo tipos nuevos (tipos definidos en C, y clases en Java)4. En cualquier caso, aqu tenemos un ejemplo de mapa de tipos para un programa que tiene tres variables declaradas; i y j con tipo int y p con tipo boolean:

tm = {i, int, j, int, ... , p, boolean}Un trato formal de comprobacin de tipos estticos de un programa se basa en la existencia deEs importante observar que la sintaxis concreta definida por BNF de un lenguaje no es adecuada para definir sus requisitos de comprobacin de tipos. Es decir, BNF no es un dispositivo lo suficientemente fuerte para expresar ideas como todas las variables declaradas deben tener nombres nicos. Esta limitacin importante de la gramtica BNF se expone normalmente en un estudio de lenguajes formales (tipo Chomsky) y se dara en un curso sobre la teora de la computacin o de compiladores. 4 Adems, las reglas para dar nombre a las variables en C y en Java son ms flexibles y complejas, teniendo en cuenta el entorno sintctico en el que se declara la variable. Volveremos a ver este tema en el Captulo 5.3

un mapa de tipos que se haya extrado de las Declarations que aparecen en la parte superior del programa. Definimos la funcin typing para J-PL0 de este modo:

typing: Declarations

TypeMapi{1,..., n}

typing ( Declarations d ) =

U d .v, d .ti i

Implementamos fcilmente esta funcin en Java as:public TypeMap typing(Declarations d) { // Coloca las variables y los tipos en un nuevo diccionario o // tabla de smbolos (map), el cual se retorna TypeMap map = new TypeMap(); for (int i = 0; i < d.size(); i++) { map.put(((Declaration)(d.elementAt(i))).v, ((Declaration)(d.elementAt(i))).t); } return map; }

De este modo, dado un Vector de Declarations di, el mtodo typing devuelve un TypeMap de Java cuyas claves son las variables declaradas di.v y cuyos valores son sus tipos respectivos di.t; en realidad, un TypeMap es una extensin de una HashTable de Java. Aqu, asumimos que la sintaxis abstracta de Declaration est definida en Java como sigue (vase el Apndice B):class Declaration { // Declaration = Variable v; Type t Variable v; Type t; public void display() { // ... } }

Podemos expresar la comprobacin de tipos estticos de un lenguaje en notacin funcional, en la que cada regla que ayuda a definir el sistema de tipos es una funcin con valor boolean V(que significa vlido). V devuelve true o false dependiendo de si un miembro en particular de una clase sintctica abstracta es vlido o no, en relacin a estas reglas. Es decir,

V: Class

B

Por ejemplo, supongamos que queremos definir la idea de que una lista de declaraciones es vlida si todas sus variables tienen identificadores nicos entre s. Podemos expresar esta idea de manera precisa del siguiente modo:

V: Declarations

B

V ( Declaratio ns d ) = i , j {1,..., n} : (i j d i .v d j .v )Es decir, cada par de variables de una lista de declaraciones tiene identificadores diferentes entre s. Recordemos que la variable i de una lista de declaraciones tiene dos partes, una Variable v y un

Tipo t. Esta particular regla de validez se dirige solamente al requisito de singularidad de lasvariables. En un lenguaje real, es importante que la definicin de esta funcin especifique tambin que el tipo de cada variable debe tomarse del conjunto de tipos disponibles (como por ejemplo, {int, boolean} para J-PL0). Dejamos esta mejora como ejercicio. Dada esta funcin, la implementacin de la comprobacin de tipos en Java no es una tarea difcil. Como base, podemos utilizar la sintaxis abstracta que se define como un conjunto de clases. Asumiendo que implementamos Declarations como un Vector de Java (vase el Apndice B), podemos ver el mtodo V de Declarations como parte de una coleccin de mtodos V que definen todos los requisitos de comprobacin de tipos estticos de un lenguaje. Aqu tenemos el mtodo V de Java para las declaraciones:public boolean V(Declarations d) { for (int i = 0; i< d.size() - 1; i++) for (int j=i+1; j -1; i++) i;

En este caso, no hay una definicin razonable para el estado final, ya que el valor de i cambia entre 1 y 0 segn el bucle se repite infinitamente. Como la sintaxis abstracta de un programa J-PL0 es una estructura de rbol cuya raz es el elemento abstracto Program, podemos definir su significado a partir de una serie de funciones, la primera de las cuales define el significado del Program.

M: Program

M(Program p) = M(p.body, {v,, undef, v2, undef, . . . , vm, undef})La primera lnea de la definicin ofrece una funcin prototipo M para el elemento del programa que estamos definiendo (en este caso, Program), mientras que la segunda define el significado de un programa, utilizando las definiciones de sintaxis abstracta de los constituyentes directos del Program.

Algunos estudios de la semntica formal definen el significado de un programa como una serie de funciones en las que el nombre (por ejemplo, MSlatemen) las distingue de las dems. En estos estudios, MSlalemenl: 2 -* 2 sena equivalente a nuestra instruccin M: x 2 -* 2- Utilizamos esta variante en la implementacin del Esquema de la semntica de J-PL0 del Captulo 8.

8

Recordemos (vase el Apndice B) que las definiciones abstractas del Program tienen dos partes; una decpart (una serie de declaraciones abstractas) y un body, que es un block. As que esta definicin funcional nos dice que el significado de un programa es el significado del cuerpo (body) del programa con el estado inicial {v1 undef, v2, undef, ..., vm, undef}. Podemos predecir que, como estas funciones de significado se extienden y se aplican a los elementos de un programa especfico, este estado inicial cambiar al asignar y reasignar valores a las variables. Este estilo funcional de definicin de significados de un programa es particularmente sencillo de implementar en Java. El siguiente mtodo de Java implementa la definicin anterior de la funcin M, asumiendo la sintaxis abstracta del lenguaje J-PL0 que definimos en el Apndice B.State M (Program p) { return M (p.body, initialState(p.decpart)); } State initialState (Declarations d) { State sigma = new State(); Value undef = new Value(); for (int i = 0; i < d.size(); i++) sigma.put(((Declaration)(d.elementAt(i))).v, undef); return sigma; }

El Captulo 4 introduce y explica la semntica de los elementos de programa abstractos restantes. En la Seccin 3.6 veremos un adelanto de dicha explicacin ilustrando la semntica formal de las expresiones e instrucciones de asignacin de J-PL0.

3.6.

EJEMPLO: SEMNTICA DE ASIGNACIONES Y EXPRESIONES DE J-PL0

En los programas imperativos abundan las expresiones y las asignaciones. Podemos expresar funcionalmente su semntica utilizando las nociones de las funciones de estado y de transformacin de estado que hemos introducido en la Seccin 3.5. Supongamos que tenemos la siguiente instruccin de asignacin concreta de J-PL0.z = x + 2 * y;

Esta instruccin se traduce en una Assignment abstracta cuya representacin mostramos en la Figura 3.4, utilizando los mtodos descritos en el Captulo 2 y la sintaxis abstracta de J-PL0 del Apndice B. Por tanto, una Assignment abstracta tiene dos partes fundamentales: una expresin source y una variable target.

Figura 3.4. Extracto de la sintaxis abstracta de la asignacin de J-PL0.

3.6.1.

Significado de las asignaciones

Podemos definir el significado de una assignment a partir de una funcin de transformacin de estado del tipo siguiente:

M: Assignment x

M(Assignment a, State ) = {a.target, M(a.source, )}

Qu nos dice esto? Es, sencillamente, un mtodo formal para describir el estado resultante de transformar el estado actual

en uno nuevo que difiere de a slo en el par cuyo primer miembro es

la variable de la izquierda de la assignment. Creamos el nuevo par combinando dicha variable con el significado de la expresin de la parte derecha de la assignment. El significado de esta expresin est definida por otra funcin M, que explicaremos en detalle en la Subseccin 3.6.2. Todo lo que tenemos que saber es que el significado de una expresin est definido por una funcin M que devuelve un valor, ya que el segundo miembro de todos los pares del estado debe ser un valor. Volvamos a nuestro ejemplo de la Figura 3.4 y asumamos que el estado actual sigue:

es como

={*,2,y,-3,z,75}De forma intuitiva, esperamos que el significado de la expresin fuente en este estado, o

M(a.source, ) = M(x + 2*y, {x, 2, y, -3, z, 75})

devolver el valor -4, ya que estamos asumiendo que M est totalmente definida para expresiones. Por tanto,

M(z = x + 2*y, {x, 2, y, -3), (z, 75}) = {x, 2, y, - 3, z, 75} {z, - 4} = {x, 2,y, -3,z, -4}

lo que completa la transformacin de estado de esta asignacin.

3.6.2.

El significado de las expresiones aritmticas

En los lenguajes de programacin reales, debemos definir cuidadosamente el significado de una

expresin aritmtica, ya que las expresiones tienen varias caractersticas que no son muy claras laprimera vez que se leen. Por ejemplo, la expresin inocua x + y/2 puede describir, en un contexto en el que x e y estn declarados como enteros, una simple divisin entre enteros seguida de una suma. Sin embargo, si x o y estn declarados con el tipo float, esta expresin podra tener distintas posibilidades de significados, ya que la divisin podra tener corno resultado un producto float, en lugar de un entero. Ms an, en algunos lenguajes, x e y podran indicar vectores o matrices, lo que le dara un significado absolutamente distinto a esta expresin. El Captulo 4 considera este tipo de problemas de forma ms cuidada e identifica los elementos que debemos aadir a la funcin de significado de las expresiones para que este tipo de situaciones estn bien definidas. En esta seccin, nicamente vamos a considerar el significado de una expresin abstracta de JPL0, as que podemos hacer algunas suposiciones bastante sencillas sobre los tipos de sus argumentos y resultado. Es decir, asumimos que la expresin slo tiene argumentos int y los cuatro operadores aritmticos (+, -, * y /) definidos en la sintaxis concreta de la expresin explicada en el Captulo 2. As, los resultados y las operaciones aritmticas individuales son del tipo int. Esta explicacin utiliza las convenciones matemticas siguientes:

v comprueba si hay un par cuyo identificador sea v en el estado actual . (v) es una funcin que extrae de el valor del par cuyo identificador sea v.

Para facilitar la definicin del significado de la expresin, definimos primero la funcin auxiliar

ApplyBinary. Esta funcin toma dos valores enteros y calcula su resultado entero. Aqu tenemos sudefinicin para los operadores aritmticos; observemos que la definicin del operador / es una forma complicada y matemtica de especificar el cociente entero de v1 dividido por v2:

ApplyBinary: Operator x Value x Value op, Value v1, Value v2)= vi + v2 = vi - v2 = vi x v2

Value ApplyBinary(Operator

si op = + si op = si op = *

v1 = floor v 2 xsigno(v1xv2)

si op = 1

Utilizando la sintaxis abstracta de la expresin ofrecida en el Apndice B, pero slo en lo que respecta a su aplicacin a expresiones que tienen operadores enteros, podemos definir el significado de una expresin as:

M : Expression x State M(Expression e, State )

Value if e es un Value if e es una Variable if e es un Binary

=e = (e) = ApplyBinary(e.op, M(e.term1, ), M(e.term2, ))

Esta funcin define el significado de una expresin en un estado en particular por casos. El primer caso extrae el valor si la propia funcin es un valor. El segundo caso extrae el valor de una variable del estado si la expresin es una variable. El tercer caso calcula un valor aplicando el operador binario adecuado a los valores de los dos trminos que acompaan al operador, caso de que la propia expresin sea binaria.

Para explicar todo esto, volvamos a nuestro ejemplo, la expresin x+2*y, y supongamos que est siendo evaluada en el estado = {x, 2, y, -3, z, 75}. Es decir, queremos utilizar estas definiciones funcionales para mostrar que M(x+2*y, {x, 2, y, -3, z, 75}) = -4. Fijmonos otra vez en la expresin x+2*y de la Figura 3.4. Como esta expresin es binaria, tenemos: M(x+2*y, {x, 2, y, - 3, z, 75}) = ApplyBinary(+, A, B) donde A = M(x, {x, 2, y, - 3, z, 75}) y B = M(2*y, x, 2, y, - 3, z, 75})

Ahora, el significado de A es el valor de x en estado = {x, 2, y, -3, z, 75}, o 2, ya que x es una variable. Sin embargo, el significado del binario B parte de otra aplicacin de la funcin M, que es: M(2*y, {x, 2, y, -3, z, 75}) = ApplyBinary(*, C, D) donde C = M(2, {x, 2, y, -3, z, 75}) El significado de C es 2, ya que 2 es un valor. El de D es -3, ya que y es una variable en estado = {x, 2, y, -3, z, 75}. Con esta informacin, la definicin de ApplyBinary nos ofrece el significado 2*y: M(2*y, {x, 2, y, -3, z, 75}) = ApplyBinary(*, 2, -3) = -6 As, el significado de nuestra expresin original queda as: M(x+2*y, {x, 2, y, -3, z, 75}) = ApplyBinary(+, 2, -6) = -4 Observemos que en este ejemplo slo hemos utilizado las definiciones de las funciones

ApplyBinary y M definidas anteriormente, junto con las propiedades matemticas de la aritmtica deenteros, para obtener este resultado. Debera estar claro que para estas funciones hay significados completamente definidos de expresiones abstractas ms complejas con operaciones binarias y operadores enteros. En el Captulo 4 explicaremos e ilustraremos varios ejemplos ms de definicin de significados para otros elementos de programa abstractos de J-PL0.

3.6.3.

Cmo implementar las funciones semnticas

La implementacin de la semntica denotativa de un lenguaje de programacin proporciona un espacio de comprobacin para la experimentacin con modelos semnticos diferentes. La implementacin de la semntica de J-PL0 en Java necesita la definicin de la clase Semantics de Java, que contiene un mtodo para cada una de las distintas funciones M y funciones auxiliares (como ApplyBinary) que, juntos, definen el significado de un programa. Los mtodos de esta clase hacen referencia a objetos definidos por la sintaxis abstracta. Por tanto, la sintaxis abstracta sirve como un puente entre la sintaxis concreta de un programa y su significado. Un grupo completo de mtodos semnticos (es decir, un grupo que defina la semntica de todas las construcciones de la sintaxis abstracta) define, en efecto, un intrprete para un lenguaje. Podemos utilizar un intrprete de este tipo para comprobar la validez de las definiciones semnticas, al igual que los intercambios que se producen entre las definiciones semnticas alternativas. Al definir un intrprete de Java, volvemos a hacer hincapi en las limitaciones del mtodo de denotacin mencionado al principio de este captulo. Es decir, para que un intrprete de Java para J-PL0 est completo, tiene que dar por supuesto que el propio Java ha sido definido formalmente. De hecho, esto es casi cierto (en documentos de investigacin recientes se ha considerado muy cuidadosamente una definicin formal de Java). Los lectores que estn interesados pueden dirigirse a [Alves-Foss 1999] para obtener ms informacin. Teniendo en cuenta esta suposicin, vamos a revisar la definicin de la clase Semantics considerando las implementaciones de las funciones semnticas M para Assignment y Expression que introdujimos en las Subsecciones 3.6.1-3.6.2. El Captulo 4 continuar este desarrollo, de forma que podamos crear un intrprete completo para el pequeo lenguaje J-PL0. Como ste es un conjunto de pares clave-valor nicos, el estado de la computacin se implementa de forma natural como una HashTable de Java, como mostramos en la Figura 3.5. Esto significa que podemos definir e inicializar la variable sigma, que representa , como sigue:

State sigma = new State();

class State extends Hashtable { public State() { } public State(Variable key, Value val) { put(key, val); } // Sobreescribe la funcin unin "onion" con dos estados s y t;

public State onion (State t) { for (Enumeration e = t.keys(); e.hasMoreElements(); ) { Variable key = (Variable)e.nextElement(); put(key, t.get(key)); } return this; }

public void display () { ... } }

Podemos implementar en Java la expresin funcional (v), que extrae de el valor de la variable v, as:sigma.get (v)

Para terminar, la operacin de unin de invalidacin, a U {(v, val)}, que sustituye o aade una nueva variable v y su valor val en el estado, puede implementarse como el mtodo onion, que contiene cdigo para insertar cada par en el nuevo estado t en la HashTable, reemplazando un par con base a una clave de concordancia o aadindolo con una nueva clave. Esta funcin ofrece un mtodo sencillo para implementar el significado M de una instruccin

Assignment a partir de una de sus definiciones:

M(Assignment a, State ) = {a.target, M(a.source, )}Es decir, implementamos el mtodo de Java de la clase Semantics como sigue:State M (Assignment a, State sigma) { return sigma.onion(new State(a.target, M (a.source, sigma))); }

El significado M de una expresin aritmtica con los operadores +, -, * y / tambin parte directamente de su definicin. Como no se producen transformaciones de estado, todo lo que pedimos es que el mtodo de Java devuelva un valor (en lugar de un estado). Recordando que el significado de una expresin aritmtica lo definimos as:

M(Expression e, State d) = e. val = (e) = ApplyBinary(e.op, M(e.term1, ), M(e.term2, )) si e es un Value si e es un Variable si e es un Binary

sugerimos la siguiente implementacin de Java:Value M (Expression e, State sigma) { if (e instanceof Value) return (Value)e; if (e instanceof Variable) if (!sigma.containsKey((Variable)e)) return new Value(); else return (Value)(sigma.get((Variable)e)); if (e instanceof Binary) return applyBinary (((Binary)e).op, M(((Binary)e).term1, sigma), M(((Binary)e).term2, sigma)); if (e instanceof Unary) return applyUnary(((Unary)e).op, M(((Unary)e).term, sigma)); return null; }

Considerando la funcin ApplyBinary, que definimos para la expresin as:

ApplyBinary(Operator op, Value v1, Value v2) = v1 + v2 si op = + = v1 - v2 si op = = v1 x v2 si op = *

tenemos la siguiente codificacin directa de Java (observemos que el cdigo de Java para la divisin por enteros es ms sencillo que la definicin matemtica).Value applyBinary (Operator op, Value v1, Value v2) { if (v1.type.isUndefined() || v2.type.isUndefined()) { return new Value(); } else if (op.ArithmeticOp( )) { if (op.val.equals(Operator.PLUS)) return new Value(v1.intValue + v2.intValue); if (op.val.equals(Operator.MINUS)) return new Value(v1.intValue - v2.intValue); if (op.val.equals(Operator.TIMES)) return new Value(v1.intValue * v2.intValue); if (op.val.equals(Operator.DIV)) return new Value(v1.intValue / v2.intValue); } ... return null; }

El mensaje de estas explicaciones es claro. La implementacin de estas funciones de significado

M en Java es relativamente sencilla, una vez que hemos establecido un esquema de representacin

para la nocin del estado. La clase HashTable de Java proporciona esta representacin directamente.

EJERCICIOS3.1. Ample la funcin de comprobacin de tipo esttico V de Declarations de forma que defina el requisito de que el tipo de cada variable sea tomado de un pequeo grupo de tipos disponibles, digamos {int, boolean}. Utilice el mismo estilo funcional y la sintaxis abstracta de las Declarations explicadas en este captulo. 3.2. Ample el mtodo Java que implementa la funcin V de Declarations de forma que implemente el requisito adicional mencionado en la Pregunta 3.1. 3.4. Complete la semntica operacional de los operadores aritmtico, boolean y lgico de J-PL0 escribiendo una regla de ejecucin para cada uno de ellos.

Apndice B.4.

FUNCIONES DE COMPROBACIN DE TIPOS DE J-PL0

A continuacin, tenemos las reglas de comprobacin de tipos estticos de J-PL0 definidas formalmente. Tambin mostramos las implementaciones de estas funciones como una coleccin de mtodos de Java. Juntas, estas funciones proporcionan una especificacin completa de un comprobador de tipos estticos para programas abstractos de J-PL0. El mapa de tipos de un Programa es un conjunto de pares, cada uno de los cuales es una Variable v y su Type t declarado.

tm = {v1, t1, v2, t2, ... , vn, tn}class TypeMap extends Hashtable { // TypeMap es implementado en un Hashtable de Java // Se adicionado el mtodo display() para facilitar la experimentacin public void display() { ... } }

La funcin typing crea un TypeMap a partir de una serie de Declarations. La implementacin de Java de TypeMap es una tabla de dispersin; como los identificadores son nicos, el mtodo put se asegura de que todas las variables declaradas y sus tipos estn representados en la tabla de dispersin.

typing: Declarations

TypeMapi{1,...,n}

typing ( Declarations d ) =

U d .v, d .ti i

public TypeMap typing(Declarations d) { // Coloca las variables y los tipos en un nuevo diccionario o // tabla de smbolos (map), el cual se retorna TypeMap map = new TypeMap(); for (int i = 0; i < d.size(); i++) { map.put(((Declaration)(d.elementAt(i))).v, ((Declaration)(d.elementAt(i))).t); } return map; }

Una serie de Declarations es vlida si sus nombres de variables son nicos. Observemos que la implementacin de Java tiene un bucle anidado ms efectivo que el que proporciona una codificacin estricta de la definicin funcional.

V: Declarations

B

V ( Declarations d ) = i , j {1,..., n} : (i j d i .v d j .v)

public boolean V(Declarations d) { for (int i = 0; i< d.size() - 1; i++) for (int j=i+1; j v2 = v1 > v2 = v1 v2 = v1 v2

si op = si op = si op = si op si op = si op = si op = si op =

< > && ||

Value applyBinary (Operator op, Value v1, Value v2) { if (v1.type.isUndefined() || v2.type.isUndefined()) { return new Value(); } else if (op.ArithmeticOp( )) { if (op.val.equals(Operator.PLUS)) return new Value(v1.intValue + v2.intValue); if (op.val.equals(Operator.MINUS)) return new Value(v1.intValue - v2.intValue); if (op.val.equals(Operator.TIMES)) return new Value(v1.intValue * v2.intValue); if (op.val.equals(Operator.DIV)) return new Value(v1.intValue / v2.intValue); } else if (op.RelationalOp( )) { if (op.val.equals(Operator.LT)) return new Value(v1.intValue < v2.intValue); if (op.val.equals(Operator.LE)) return new Value(v1.intValue = v2.intValue); if (op.val.equals(Operator.GT)) return new Value(v1.intValue > v2.intValue); } else if (op.BooleanOp( )) { if (op.val.equals(Operator.AND)) return new Value(v1.boolValue && v2.boolValue); if (op.val.equals(Operator.OR)) return new Value(v1.boolValue || v2.boolValue); } return null; }

ApplyUnary: Operator x Value

Value si op = !

ApplyUnary (Operator op, Value v) = ~v

Value applyUnary (Operator op, Value v) { if (v.type.isUndefined()) return new Value(); else if (op.val.equals("!")) return new Value(!v.boolValue); return null; }

El significado de una Expression es un Value, el valor de una Variable en el estado actual o el resultado de la aplicacin de un operador binario o unario a los significados de sus operandos en el estado actual.

M: Expression x =e (e)

Value si e es un Value si e es una Variable si e es un Binary si e es un Unary

M(Expression e, State )

ApplyBinary(e.op, M(e.term1, ), M(e.term2, )) = ApplyUnary(e.op, M(e(.ierm, )))

Value M (Expression e, State sigma) { if (e instanceof Value) return (Value)e; if (e instanceof Variable) if (!sigma.containsKey((Variable)e)) return new Value(); else return (Value)(sigma.get((Variable)e)); if (e instanceof Binary) return applyBinary (((Binary)e).op, M(((Binary)e).term1, sigma), M(((Binary)e).term2, sigma)); if (e instanceof Unary) return applyUnary(((Unary)e).op, M(((Unary)e).term, sigma)); return null; }

El significado de una instruccin es, a su vez, el significado del tipo determinado de instruccin que representa.

M: Statement x

si s es un Skip si s es una Assignment si s es una Conditional si s es un Loop si s es un Block = M((Assignment)s, ) = M((Conditional)s, ) = M((Loop)s, ) = M((Block)s, )

M(Statement s, State ) = M((Skip)s, )

State M (Statement if (s instanceof if (s instanceof if (s instanceof if (s instanceof if (s instanceof return null; }

s, State sigma) { Skip) return M((Skip)s, sigma); Assignment) return M((Assignment)s, sigma); Conditional) return M((Conditional)s, sigma); Loop) return M((Loop)s, sigma); Block) return M((Block)s, sigma);

El significado de una instruccin Skip es, realmente, la funcin de identidad, ya que no cambia el estado actual del clculo.

M(Skip s, State ) = State M (Skip s, State sigma) { return sigma; }

El significado de una Assignment es la unin de invalidacin del estado actual y un par nuevo formado por la Variable que es el objetivo de la Assignment y el significado de la Expression que es el origen.

M: Assignment x

M(Assignment a, State ) = {a.target, M(a.source, )}

State M (Assignment a, State sigma) { return sigma.onion(new State(a.target, M (a.source, sigma))); }

El significado de un Block es, o la funcin de identidad (si no tiene instrucciones), o el significado del resto del Block aplicado al estado nuevo conseguido mediante la ejecucin de la primera instruccin del Block. Podemos implementar esto con un bucle f o r o con un mtodo recursivo.

M(Block b, State ) = = M((Block)b2n,

si b = 0

M((Statement)b1, , ))

si b = b1 , b2 . . . bn

State M (Block b, State sigma) { for (int i=0; i v2.intValue); }

else if (op.BooleanOp( )) { if (op.val.equals(Operator.AND)) return new Value(v1.boolValue && v2.boolValue); if (op.val.equals(Operator.OR)) return new Value(v1.boolValue || v2.boolValue); } return null; } Value applyUnary (Operator op, Value v) { if (v.type.isUndefined()) return new Value(); else if (op.val.equals("!")) return new Value(!v.boolValue); return null; } Value M (Expression e, State sigma) { if (e instanceof Value) return (Value)e; if (e instanceof Variable) if (!sigma.containsKey((Variable)e)) return new Value(); else return (Value)(sigma.get((Variable)e)); if (e instanceof Binary) return applyBinary (((Binary)e).op, M(((Binary)e).term1, sigma), M(((Binary)e).term2, sigma)); if (e instanceof Unary) return applyUnary(((Unary)e).op, M(((Unary)e).term, sigma)); return null; } State M (Statement if (s instanceof if (s instanceof if (s instanceof if (s instanceof if (s instanceof return null; } s, State sigma) { Skip) return M((Skip)s, sigma); Assignment) return M((Assignment)s, sigma); Conditional) return M((Conditional)s, sigma); Loop) return M((Loop)s, sigma); Block) return M((Block)s, sigma);

State M (Skip s, State sigma) { return sigma; } State M (Assignment a, State sigma) { return sigma.onion(new State(a.target, M (a.source, sigma))); } State M (Block b, State sigma) { for (int i=0; i