UNIVERSIDAD POLITÉCNICA DE MADRID ESCUELA UNIVERSITARIA DE INFORMÁTICA TRABAJO FIN DE CARRERA SOBRE LA INFORMACIÓN, SU ESTRUCTURA Y SU-GESTIÓN MICAEL GALLEGO CARRILLO JULIO 2002 LUIS FERNÁNDEZ MUÑOZ Indice I. INTRODUCCIÓN ................................................................................ 1 II. MECANISMOS RECURRENTES DE LA ESTRUCTURA DE LA INFORMACIÓN .................................................................................... 9 1. Comparación de lenguajes y paradigmas ............................................................9 2. Mecanismos de construcción de estructuras de datos en lenguajes de programación ....................................................................................................................16 3. Representaciones Gráficas ......................................................................................21 4. Mecanismos de construcción de estructuras de datos de otros sistemas de representación de la información ...................................................................................36 5. Mecanismos de construcción de la estructura de las sentencias en los lenguajes de programación .............................................................................................41 6. Mecanismos de construcción de los lenguajes de programación .....................43 7. Axiomática de la información ................................................................................45 III. MODELADO DE PASCAL .............................................................. 48 1. Introducción..............................................................................................................48 2. Tiempo de Compilación..........................................................................................49 3. Tiempo de Ejecución ...............................................................................................87 IV. LAS INNUMERABLES VENTAJAS Y EL SUPUESTO INCONVENIENTE CON SUS INNUMERABLES DESVENTAJAS..... 107 1. Entornos de Desarrollo ...........................................................................................108 2. Procesadores de lenguajes ......................................................................................115 3. Prototipado de lenguajes de programación .........................................................118 4. Ingeniería del Software ...........................................................................................123 5. Asistencia a la educación ........................................................................................127 6. Interfaces gráficos de usuario ................................................................................130 7. La eficiencia, el inconveniente y sus innumerables desventajas.......................131 V. CONCLUSIONES................................................................................ 135 VI. BIBLIOGRAFÍA................................................................................. 137 ANEXO A. FUENTES .............................................................................. 140 ANEXO B. ESPECIFICACIÓN EN JAVACC....................................... 223 ANEXO C. DIAGRAMAS UML............................................................. 231 I. Introducción En los planes de estudio de las de las ingenierías de informática he observado muchos aspectos referentes a la programación que resultan curiosos e interesantes. Desde un punto de vista personal intentaré explicar cuáles son estos aspectos. El objetivo de la realización de este TFC es poner en orden todos ellos y aclararlos. Sin embargo, esta tarea es muy ambiciosa así que intentaremos sentar una base para seguir trabajando en el futuro. Realizando los primeros cursos de la ingeniería técnica de sistemas siempre sentí gran atracción por la idea de la visualización del software. Esta rama de la informática estudia cómo visualizar los conceptos presentes en la programación, tanto en la fase de desarrollo como en la fase de ejecución. A mi entender muchos conceptos introducidos en las primeras asignaturas de programación serían bastante más comprensibles y permitirían al alumno una mayor rapidez de aprendizaje. Ya en ese momento empecé a pensar cómo se podría construir un sistema que permitiese eso, visualización de código fuente y un programa en ejecución. Lamentablemente, en aquellos cursos, mi base teórica y conocimientos prácticos distaban mucho de los necesarios para plantearme la construcción de 1 Sobre la información, su estructura y su-gestión un sistema con esas funciones. Tener un sistema de estas características me permitiría incluir el código fuente en las memorias de las prácticas con un poco mas de formato que el simple código en texto plano. La creación de memorias para los programas suponía mucho esfuerzo. La documentación de un código era una tarea muy tediosa, por la dificultad de ir generando la documentación de una manera cómoda a la vez que se programaba. Me gustaría que el sistema asociara la documentación al código fuente de forma asistida. La inclusión de comentarios en el código fuente me parecía un método bastante rudimentario de generar documentación. Con este método es necesario tener todo el código para entender el comentario. Otras inquietudes me llegaban a medida que iba conociendo lenguajes de programación del paradigma procedural, declarativo y orientado a objetos. Notaba una asombrosa similitud en muchos de los conceptos e ideas que observaba en cada uno de ellos. Sin embargo no comprendía que no hubiese una terminología común para referirse a dichos conceptos y prácticamente ningún documento que sirviese de puente entre lenguaje del mismo paradigma e incluso entre lenguajes de distintos paradigmas. Por otro lado, a medida que construía programas cada vez mas grandes, con miles de líneas de código observaba la dificultad de gestión de dicho código. No me refiero a nivel lógico, de gestión de los tipos del programa, las variables globales, etc... sino a nivel práctico. Cada vez era mas difícil navegar entre las ingentes cantidades de código con el añorado TurboPascal 5.0. Algo tan sencillo como poder saltar de la llamada a un procedimiento a su implantación era una cosa que sólo ocurría en mi cabeza, pero no en los entornos de programación. Con la forma textual de programar también comencé a tener mis dudas. Comencé a darle vueltas a la programación visual, en la cual todos los elementos que se pueden ir incorporando al programa, como definición de 2 Sobre la información, su estructura y su-gestión subprogramas, referencias a variables, etc... se gestionase de forma visual. En ese momento consideraba que reduciría considerablemente la curva de aprendizaje de un nuevo lenguaje y a la vez que mejoraría la gestión del código. Cuando empecé a descubrir las ventajas de la programación orientada a objetos con la asignatura Programación II. Concebí una nueva forma de programar. Sin ser rigurosos, se puede decir que la programación orientada a objetos se basa en representar los conceptos que se manejan en el dominio de la aplicación como clases y en materializaciones de esos conceptos como objetos. Este acercamiento permite una mayor abstracción y una forma de programar más cercana a los conceptos humanos. Descubrí el lenguaje Java y con él como el problema de la documentación a medida que se realizaba el código se había tenido en cuenta y se creó JavaDoc. JavaDoc es un convenio que permite generar documentación a partir de los comentarios escritos en código fuente con una determinada sintaxis. La herramienta que lo permite genera documentación en formato HTML. Aunque esos comentarios siguen escribiéndose de forma textual. Mas adelante comentaremos el estado actual de los entornos de desarrollo integrados (IDE) y que cosas han ido incorporando para manejo de todas estas posibilidades. Pero la creación de documentación se limita a lo que Sun Microsystem consideró necesario para documentar un código fuente orientado a objetos. No hay posibilidad de personalizar la estructura de la documentación, para documentar aquello que crees conveniente. La representación gráfica de la estructura de un código fuente, aunque matizándolo mucho, es posible con UML. Este lenguaje unificado de modelado con representación gráfica tiene su principal aplicación en los programas orientados a objetos. Pero existen muchas dificultades para encontrar software 3 Sobre la información, su estructura y su-gestión actualmente (15 Marzo 2002) que permitan gestionar el código de manera gráfica y cómoda usando este convenio. En la carrera nos enseñan a modelar los elementos de un dominio concreto. Creamos aplicaciones con gráficos, aplicaciones de bases de datos, aplicaciones web, etc... Estas aplicaciones se usan en diseño gráfico, en la gestión de las pymes, y en muchos más lugares. Generalmente estos programas manejan información de estructura muy compleja. Si la información que manejan es bastante sencilla, estos programas manejan cantidades ingentes de datos. Tenemos numerosas técnicas a nuestro alcance para gestionar este tipo de información, a la que podríamos llamar de “usuario”. Sabemos hacer estupendos interfaces gráficos y somos capaces de hacer muy sencilla la navegación por cantidades ingentes de información. Hemos estudiado formas de modelar información, de obtener las características esenciales y estructurales de ésta para construir programas que la gestionen. Además se requiere que estos programas, en la medida de lo posible, sean fácilmente adaptables. Todos los estudios se centran en manejar los datos del usuario, y sin embargo, ¿que ocurre con los datos que manejamos nosotros los programadores? En la carrera sólo nos dan las ideas básicas de como se representa la estructura de una información textual, en la asignatura de Teoría de Autómatas y Lenguajes Formales. Pero nada de gestión del código, nada de cómo se manejan programas de miles y miles de líneas. Seguimos programando en modo texto, como en los años 50. ¿Dónde están los gráficos? Afortunadamente en algunos entornos comenzamos a tener resaltado de sintaxis, pero... ¿es esto suficiente para gestionar un código fuente? Sabemos manejar cualquier tipo de información excepto la que nosotros creamos y manejamos, los códigos fuente. Por todos estos motivos la creación de un intérprete y un compilador me llamaba poderosamente la atención. Pero como hemos comentado, la asignatura de Teoría de Autómatas y Lenguajes formales no estudia cómo modelar un 4 Sobre la información, su estructura y su-gestión código fuente, si no se basa en definir en modo textual una secuencia correcta de caracteres. Por otro lado, veo que en muchos lenguajes aparecen estilos de programación. Pero no hay una manera formal de gestionarlos, lo que hace que sean difíciles de manejar. Me refiero a convenciones en cuanto a cual es la estructura del nombre de una variable o de una clase. O de que forma se deberían de poner las llaves. Estas cosas pueden parecer superfluas, pero tienen bastante importancia. También existen convenciones de una naturaleza superior. Por ejemplo, el modelo de eventos en Java, se basa en una serie de convenciones con los nombres de los métodos y de las clases que representan a los eventos. Además, mucho del código para implementar los eventos tiene la misma estructura. De una forma similar, la especificación de JavaBeans usa convenciones en nombres y crea clases “hermanas” para representar información. Pero todos estos convenios, aparecen en tutoriales y sólo algunos entornos permiten gestionarlos. De un nivel superior tenemos los patrones de diseño orientado a objetos. Conjuntos de objetos y clases, que dispuestos de determinada forma consiguen un comportamiento con un nivel mayor de abstracción. Aunque existen lenguajes de patrones, para la gestión de código con patrones no existe en ningún entorno. Según hemos observado, se demanda “básicamente” una forma de gestionar el código de la misma forma que sabemos gestionar cualquier otro tipo de información. Necesitamos estudiar en los mismos términos tanto datos de “usuario” como sus metadatos (nombre de las variables donde se encuentran esos datos, su tipo, etc...) y los datos que manejan a los datos de usuario (instrucciones). Existen multitud de similitudes entre ellos y a continuación 5 Sobre la información, su estructura y su-gestión indicaremos una serie de ejemplos en los que se ven mucho mas claro estas similitudes. Por lo que hemos estado comentando hasta ahora, parece que únicamente queremos gestionar los metadatos (nombre de variables, tipo, etc...) y la distribución del código para labores de desarrollo de programas. Parece que este estudio sólo tiene interés para la labor de creación de software pero que poco o nada tiene que ver con el software en fase de explotación. Pero existen muchos mas casos de los que parecen en los cuales este estudio sería muy útil en la propia ejecución del código. En ingeniería del software, la forma de comprobar si la compañía desarrolladora de software a comprendido bien los requisitos del cliente es presentándole pantallas prototipo de la aplicación, esto es porque las pantallas muestran una parte muy importante de una aplicación y son casi un fiel reflejo de las estructuras (clases) y objetos que aparecen en el programa. El desarrollo de software en el proceso propuesto por los creadores de UML se basa mucho en los casos de uso, en aquellas acciones que se hacen desde “fuera” del sistema y que le alteran o permiten su consulta. De alguna forma, todos aquellos elementos del sistema que se crean siguiendo los casos de uso van a tener una representación gráfica asociada. En muchos casos, la representación de un objeto de una clase depende tanto de su estructura interna, es decir de los atributos y método de su clase, que podría generarse de forma asistida código para la visualización partiendo de la clase del objeto. Por ejemplo si nuestra aplicación trabaja con alumnos, obviamente tendremos la clase alumno, con atributos como nombre, foto, apellido1, domicilio, población... o algunos atributos mas raros como, media primera fase, códigoA, tutor. Si nosotros quisiéramos representar los datos que tenemos de un alumno, lo mas lógico es que pusiéramos a un lado el nombre de cada atributo y al otro lado la representación de cada uno de los atributos. 6 Sobre la información, su estructura y su-gestión Observamos como un metadato, el nombre de un atributo, se convierte en un dato de la aplicación ya que necesita mostrarse por pantalla. Pero si deseáramos asociar una descripción detallada de cada uno de los atributos... ¿ que estructura usaríamos ? Esta descripción puede ser la misma descripción que aparezca en la propia documentación del código fuente. De alguna forma, lo ideal sería guardar esa información en el lugar (no sabemos de momento cual) donde se guarda el nombre del atributo. En ese lugar también necesitaríamos una descripción. Otro ejemplo sería la creación de un recubrimiento gráfico para un algoritmo de clasificación, por poner un ejemplo. La forma de comunicarnos con el algoritmo es mediante parámetros, y mediante el lanzamiento del mensaje en donde vayan los parámetros. Imaginemos que simplemente estamos depurando el código de dicho algoritmo y queremos una representación gráfica medianamente aceptable. El tiempo necesario para crear dicha representación sería muy grande. Básicamente en la interfaz gráfica aparecerían los nombres los parámetros, un botón que permitiera lanzar el mensaje y una forma de crear gráficamente cada uno de los parámetros necesarios y luego poder mostrar gráficamente el valor devuelto. Es decir, necesitaremos una representación del objeto devuelto (volvemos al ejemplo del alumno) y una forma gráfica de crear un objeto. La forma gráfica de crear un objeto puede ser la escritura de cada uno de los parámetros del constructor. Esta pequeña aplicación de prueba del algoritmo se podría convertir en un vistoso applet sin apenas esfuerzo y gran parte de ese applet se podría construir automáticamente. Vemos que por uno u otro lado, el estudio pormenorizado de los conceptos de cualquier lenguaje de programación es muy necesario. Por tanto el primer paso en la construcción de un sistema que permita la gestión de los metadatos y 7 Sobre la información, su estructura y su-gestión los datos que manejan datos comienza con el modelado de los conceptos que aparecen en un lenguaje de programación. Basándome en todas estas ideas e inquietudes he decidido crear este TFC. El trabajo va a consistir en analizar las similitudes entre los datos y el código fuente, presentar un prototipo que modela un lenguaje como datos de “usuario” y ver las ventajas de este enfoque en la construcción de herramientas de desarrollo. En el capítulo 2 se estudiará el gran parecido que tienen todos los lenguajes de programación entre sí. También se hará patente la gran similitud en la forma que tienen de modelar la información. Como último punto veremos como el código y los datos son estructuralmente equivalentes. En el capítulo 3 modelaremos el lenguaje Pascal e implantaremos un prototipo en el que se ponen en práctica la idea de la equivalencia entre datos y código. En el capítulo 4 veremos las ventajas que supondría construir herramientas basadas en el modelado propuesto. Estas mejoras abarcan los entornos de desarrollo, compiladores, soporte a la ingeniería del software, aplicación en enseñanza de la programación, etc… 8 II. Mecanismos recurrentes de la estructura de la información 1. Comparación de lenguajes y paradigmas A lo largo de la carrera he aprendido distintos lenguajes de programación. Algunos de estos lenguajes de programación son Pascal, C, Pascal-FC, PIIPOO, Java, Delphi, LISP, CAML, Prolog, etc... A medida que aprendía cada uno de los lenguajes de programación notaba como se parecían en sus aspectos más importantes. Vamos a clasificarlos dependiendo del paradigma al que pertenecen. 9 Sobre la información, su estructura y su-gestión Paradigma Lógico (Declarativo) Prolog Paradigma Orientado a Objetos Paradigma Concurrente Parlog Prolog++ Vulcan Paradigma Funcional (Declarativo) LISP CAML Haskel Clean Paradigma Imperativo Java CLOS Smalltalk C++ ObjetivePascal Eiffel Pascal PascalFC Ahora vamos a repasar las similitudes que tienen los lenguajes del mismo grupo, y posteriormente veremos las similitudes que presentan entre los distintos grupos. Lenguajes Imperativos Procedurales Las diferentes sentencias son equivalentes entre C y PASCAL, salvando las diferencias sintácticas y potencia del lenguaje: Descripción Asignación Una C Pascal variable a = 3; A:= 3; llamada a de tipo entero a la que se le asigna un valor. Expresión Una expresión de 5 % 2 tipo entero 5 MOD 2 de valor 1 10 Sobre la información, su estructura y su-gestión Llamada función a Llamada la close(); a CLOSE; función close que no tiene parámetros. Sentencia If Dependiendo del if( a == 2 ){ valor de a incrementa IF A = 2 THEN BEGIN A := A + 1; END ELSE BEGIN A := A + 2; END; a++; } else { se a += 2; su } valor en 1 o en 2. Bucle For Bucle for imprime secuencia que for(i=1,i<=5;i++){ pritnf(“ %d”,i); la } FOR I:=1 TO 5 DO BEGIN WRITE(‘ ‘,I); END de números 1 2 3 4 5 Bucle While Bucle while que i = 0; while( num[i]!=0 ){ printf( num[i] ); lee de la entrada i++; estándar y saca } I := 0; WHILE NUM[I]<>0 DO BEGIN WRITE( NUM[I] ); I:=I+1; END; una a hasta hasta que se lee un 0. Los tipos primitivos en ambos lenguajes son similares. Los tipos que se pueden crear son también similares. Vamos a comparar sus constructores. Descripción C Pascal Registro Definimos un registro typedef struct fecha{ TYPE int dia; FECHA = RECORD con tres campos para int mes; DIA: INTEGER; representar una fecha. int anno; MES: INTEGER; }; ANNO: INTEGER; END; Registro Definimos un registro typedef fechaNumero { con 2 partes para int dia; representar una fecha, int mes; en formato numérico int anno; o como un array de Variante 11 struct TYPE FECHANUMERO = RECORD DIA: INTEGER; MES: INTEGER; ANNO: INTEGER; Sobre la información, su estructura y su-gestión caracteres. }; END; typedef fechaTexto[40]; char FECHATEXTO = ARRAY [1..40] OF CHAR; typedef union fecha{ struct fechaNumero fechaNumero; struct fechaTexto fechaTexto; } Array Referencia Definimos un array de enteros de longitud 20 para almacenar medidas. Definimos una referencia a un entero. typedef int medidas[20]; typedef int *referencia; FECHA = RECORD CASE TIPO:INT OF 0:(FECHANUMERO: FECHANUMERO); 1:(FECHATEXTO: FECHATEXTO); END; TYPE MEDIDAS = ARRAY [1..20] OF INTEGER; TYPE REFERENCIA = ^INT; Existen algunas diferencias entre los tipos de C y de Pascal. Estas diferencias se basan en que C es más cercano a la máquina que Pascal y debido a ello C no controla cosas que Pascal si controla. Pascal es un lenguaje de un nivel más alto que C, pero mantiene su esencia. Lenguajes Imperativos Orientados a Objetos Vamos a hacer una breve comparación entre Java y PIIPOO. Las sentencias de control de flujo de ejecución, la asignación, el lanzamiento de mensajes y las expresiones son bastante similares. Descripción Una variable llamada a de tipo entero a la que se le asigna un valor. Una expresión de tipo Expresión entero de valor 1 Llamada a Llamada a la función close que no tiene función parámetros. Asignación Sentencia If Java PIIPOO a = 3; A:= 3; 5 % 2 5 MOD 2 fichero.close(); FICHERO.CLOSE; if( a == 2 ){ Dependiendo del a++; valor de a se } else { incrementa su valor en a+=2; 1 o en 2. } 12 IF A = 2 THEN BEGIN A := A + 1; END ELSE Sobre la información, su estructura y su-gestión BEGIN A := A + 2; END; Bucle For Bucle for que imprime for(i=1,i<=5;i++){ System.out.println(“ la secuencia de números 1 2 3 4 5 ”+i); } Bucle While Bucle while que lee de i = 0; la entrada estándar y saca una a hasta hasta que se lee un 0. while( num[i]!=0 ){ printf( num[i] ); i++; } FOR I:=1 TO 5 DO BEGIN (‘ ‘+I).WRITE; END I := 0; WHILE NUM[I]<>0 DO BEGIN WRITE( NUM[I] ); I:=I+1; END; En Java existen tipos primitivos con diferente uso que las clases. En PIIPOO no existen tipos primitivos, el sistema ofrece unas clases primitivas. En cuanto a los nuevos tipos que se manejan existen más diferencias que en el caso de C y Pascal, pero también son similares. Vamos a comparar sus constructores. Descripción Clase Array Java PIIPOO class fecha { Definimos una clase int dia; con tres atributos para int mes; representar una fecha. int anno; } CLASS IMPLEMENTATION FECHA; ATRIBUTES DIA:INTEGER; MES:INTEGER; ANNO:INTEGER; END CLASS IMPLEMENTATION TYPE Definimos un array de int medidas[]; = ARRAY enteros de longitud 20 Java no permite definir tipos MEDIDAS [1..20] OF INTEGER; que sean arrays. En la para almacenar declaración de tipo no se medidas. indica el tamaño. Referencia Definimos una Java no permite referencia a un entero. explícitamente el uso de TYPE REFERENCIA = ^INT; referencias. Se puede ver que existen múltiples parecidos entre lenguajes del mismo paradigma. De hecho se han creado traductores para convertir programas escritos en un lenguaje en programas en el otro lenguajes. Se pueden encontrar traductores entre C y Pascal. 13 Sobre la información, su estructura y su-gestión Lenguajes Declarativos Funcionales Con el paradigma funcional ocurre lo mismo, vamos a comparar las distintas instrucciones de LISP y CAML. Como se trata de un paradigma funcional las únicas instrucciones que existen son las llamadas a funciones y las expresiones como un caso particular de estas. Descripción Expresión LISP CAML Una expresión de ( + 5 2 ) tipo entero 5 + 2 de valor 7 Llamada función a Llamada a la ( eleva 5 2 ) eleva(5,2) función eleva con dos parámetros. Ambos lenguajes constan de tipos primitivos. CAML, entre otros tiene el tipo entero, real, booleano, etc.... En LISP los tipos más representativos son el símbolo, la cadena de caracteres y el valor numérico. A parte de los tipos primitivos de datos de los dos lenguajes, ambos tienen tipos compuestos. En LISP el tipo compuesto por excelencia es la lista de elementos, para la cual se han creado primitivas de acceso. En el caso de CAML también existe el tipo compuesto lista de elementos. Además en CAML se pueden tener tuplas. Lenguajes Declarativos Lógicos El lenguaje lógico impartido en la escuela es PROLOG, la construcción de programas en este lenguaje parte de la lógica matemática y el uso de predicados. Pese a la aparente diferencia con los lenguajes funcionales como CAML y LISP, de todos es sabido la existencia de conversores entre lenguajes de ambos paradigmas. 14 Sobre la información, su estructura y su-gestión Similitudes entre paradigmas Sin un estudio riguroso y profundo hemos encontrado muchos parecidos entre los distintos lenguajes de un mismo paradigma y los lenguajes de los distintos paradigmas. Las instrucciones de control de flujo de ejecución en los lenguajes imperativos son prácticamente iguales. En todos los lenguajes imperativos que hemos “repasado” aparecen la sentencia alternativa, if, y varias formas de la secuencia iterativa, for, while, repeat, etc... También se permite en todos los lenguajes imperativos la construcción de rutinas (funciones y procedimientos). La forma de ejecutar dichas rutinas, es decir, la llamada, es también similar en todos los lenguajes. La asignación también está presente en todos ellos. En los lenguajes del paradigma declarativo, aunque los hemos visto muy superficialmente, se aprecian muchas diferencias. En funcional existen las funciones como piezas para la construcción de programas; de igual forma, en PROLOG, el lenguaje lógico, las piezas de construcción son los predicados, similares a las funciones. La forma de construir estructuras de datos complejas es muy similar entre los lenguajes del mismo paradigma. En el paradigma imperativo aparecen el array y registro o clase como elementos principales. La referencia también juega un papel importante en todos ellos, incluido Java aunque su tratamiento en Java no sea explícito. En todos los lenguajes declarativos ocurre una cosa similar, el tipo lista aparece como elemento clave en la construcción de estructuras de datos. A parte de las similitudes entre lenguajes del mismo paradigma, encontramos grandes parecidos entre los lenguajes del paradigma imperativo y el declarativo. La sentencia de control de flujo de ejecución alternativa, el if, se representa en ambos paradigmas. En el paradigma imperativo se representa mediante una instrucción concreta. En el paradigma declarativo, existen funciones con el mismo funcionamiento. 15 Sobre la información, su estructura y su-gestión Las sentencias iterativas de la programación imperativa, como son los bucles for, while, etc... se pueden “simular” en la programación declarativa usando la recursividad y el correspondiente caso base. Las primitivas de construcción de estructuras de datos también son similares. Las listas y los arrays son muy parecidos, con la salvedad de la longitud fija ligada a los arrays y la longitud variable de las listas de elementos. Los registros también aparecen en los lenguajes imperativos en los RECORD de Pascal, struct de C, etc y en los lenguajes funcionales en las tuplas de CAML. Hemos visto como entre todos los lenguajes estudiados, siendo de distintos paradigmas existen similitudes. Las similitudes se manifiestan en las dos partes en las que se divide un programa. Los lenguajes son similares en los mecanismos para formar estructuras de datos y en los mecanismos para controlar el flujo de ejecución de instrucciones. 2. Mecanismos de construcción de estructuras de datos en lenguajes de programación Hemos visto los mecanismos o primitivas que tienen los distintos lenguajes de los distintos paradigmas para construir estructuras complejas de datos a partir de sus tipos primitivos. En cada uno de los lenguajes dichas primitivas tienen nombres distintos. Además la semántica de cada una de ellas puede diferir de un lenguaje a otro. Por estos motivos vamos a hacer una recopilación de dichas primitivas y vamos a darles un nombre independiente del usado en cada uno de los lenguajes. 16 Sobre la información, su estructura y su-gestión Tipos atómicos En todos los lenguajes de programación existen los tipos simples. Los tipos simples, entre otras cosas, definen un conjunto de valores. Los tipos simples mas comunes son el entero, el decimal y el carácter. Muchos lenguajes tienen como tipos simples la cadena de caracteres, el tipo lógico, etc... En todos los lenguajes se pueden manejar datos cuyo tipo sea uno de los atómicos. En los lenguajes imperativos los valores se albergan en variables. En los lenguajes declarativos, los valores se transmiten de la salida de una función a la entrada de otra función. Registro En Pascal se permite crear agrupaciones de datos de tipos distintos. Esta agrupación se puede tratar como un todo o bien acceder a cada una de sus partes. Sus partes son accesibles mediante un identificador. En Pascal están los record. En C tenemos los struct con la misma funcionalidad que la vista en Pascal. En los lenguajes orientados a objetos se obtiene la misma funcionalidad con las clases y sus atributos. En CAML no se accede a los atributos mediante un identificador, si no que se accede a ellos mediante un patrón. El acceso mediante patrón básicamente asocia un nombre al elemento en el momento del acceso y no en el momento de la creación del tipo. Se puede decir que se tiene un acceso híbrido entre posición e identificador. En Lisp y Prolog no se tiene un tipo directamente equivalente. Para obtener una funcionalidad similar se usa el tipo lista, ya que en estos lenguajes no tienen comprobación de tipos en tiempo de compilación y los elementos de la lista pueden tener cualquier tipo distinto unos de otros. Cuando en Lisp y Prolog se usan las listas como registros, debido a que cada elemento de la lista representa un elemento similar al registro, el número de elementos suele ser fijo. Cuando definimos un tipo registro estamos indicando que cada una de las variables de ese tipo estará compuesta por varias variables. Cada una de ellas 17 Sobre la información, su estructura y su-gestión tendrá un tipo determinado. El número de variables por los que se forme será fijo. Secuencia En todos los lenguajes de programación se permite crear agrupaciones de datos del mismo tipo. En C y en Pascal se tienen los arrays, en Java y PIIPOO también tenemos los arrays. En los lenguajes funcionales LISP y CAML usamos las listas para este fin. Hay que destacar, que en LISP los elementos de la lista no están obligados a tener el mismo tipo, pero un uso bastante común de ellas es que tengan todos el mismo tipo. El tamaño de la secuencia en algunos lenguajes se fija en el tipo, como es el caso de Pascal y C. Y en otros casos puede ser determinado cuando se crean las variables del tipo, como en el caso de Java. En LISP y CAML el tamaño se varía en tiempo de ejecución. Por tanto podemos decir que el tamaño es potencialmente variable. Los elementos de la secuencia están ordenados. El acceso a ellos se hace siempre por su posición, bien sea indicando el índice, en el caso de los lenguajes imperativos, o bien tratando la cabeza de la lista en los lenguajes declarativos. Cuando definimos un tipo secuencia estamos indicando que cada una de las variables de ese tipo estará compuesta por varias variables, todas ellas del mismo tipo. Podemos usar la siguiente representación gráfica para representar el tipo secuencia: Referencia Con este nombre queremos identificar al concepto de puntero que aparece en algunos lenguajes de programación. Aunque no es obligatorio, generalmente en los lenguajes de programación se almacenan los elementos de un array de forma contigua en memoria. Los elementos de un registro suelen también almacenarse de forma contigua en memoria. Debido a esta implementación no 18 Sobre la información, su estructura y su-gestión se puede compartir el mismo dato en dos lugares distintos de la estructura de datos. Vamos a ver un ejemplo de la idea de referencia. Supongamos que deseamos representar una agenda de teléfonos. Podemos definir un tipo registro llamado regPersona con los campos nombre, apellidos, dirección, teléfono, etc. Posteriormente podemos definir una secuencia de datos del tipo regPersona, para poder almacenar todas las entradas de la agenda. Si ahora queremos representar en cada uno de los registros que mantienen los datos de una persona quién es su padre dentro de los que están almacenados en la agenda, ¿cómo lo hacemos? Podemos tener un campo del registro regPersona con el tipo regPersona de nuevo y en él almacenar los datos del padre. Pero… ¿que ocurre si varias personas tienen el mismo padre? Estarían los datos del padre duplicados en nuestra estructura de datos. Esto traería problemas a la hora de modificar cualquier dato, ya que habría que modificarle en todos los lugares donde se almacene el padre. También sería un desperdicio de espacio. Por estos motivos se crean las referencias. Podríamos decir que una referencia es un valor, comprensible por el sistema o por el programador, que identifica inequívocamente a un dato concreto. De esa manera, cuando tenemos ese valor, podemos identificar un único dato, pero sin tener el dato propiamente dicho; en vez de el dato tenemos un acceso al dato. Una representación bastante habitual de las referencias es mediante una flecha desde el lugar donde está la referencia al lugar donde está el dato. En los lenguajes de programación imperativos como C o Pascal las referencias se implementan con punteros. En el caso de PIIPOO ocurre lo mismo. Java en cambio es distinto en este sentido. En Java los objetos son accedidos siempre a través de referencias, nunca se tiene el dato propiamente dicho. En los lenguajes declarativos, la referencia la ha de implementar el programador usando identificadores explícitos. 19 Sobre la información, su estructura y su-gestión Cuando definimos un tipo referencia estamos indicando que cada una de las variables de ese tipo contendrá un valor que nos permitirá acceder al dato concreto que identifican. Alternativa Los tipos alternativa se definen indicando dos o más tipos existentes. Cuando definimos un tipo alternativa estamos indicando que cada una de las variables de ese tipo contendrá un valor perteneciente a uno de los tipos indicados en la definición. La alternativa se permite en Pascal a través de los registros variantes. En C una funcionalidad parecida se obtiene con las uniones. En la programación orientada a objetos la alternativa se obtiene mediante la herencia en la creación de tipos y el polimorfismo, de forma que una variable declarada de un tipo admite debido al polimorfismo cualquier valor que sea de un tipo derivado de ese tipo. En Lisp no hay comprobación de tipos en tiempo de compilación porque se posibilita la alternativa de cualquier símbolo asociado a un átomo o lista. El tipo de un valor se comprueba justo en el momento en el que se va a usar dicho valor. Por tanto se puede hacer uso de esta alternativa pero será controlada en todo momento por el programado Pongamos un ejemplo de la construcción de un programa usando la alternativa. Por ejemplo, para saber donde vive una persona podemos almacenar un texto con el nombre de su pueblo o bien el código postal. Por tanto, localidad es una alternativa entre entero o cadena de caracteres. Podemos hacer un breve resumen de las características más importantes de cada uno de los mecanismos: 20 Sobre la información, su estructura y su-gestión Mecanismos de construcción de estructuras de datos Átomo Representa a un dato atómico. No se puede dividir en otros datos. Registro Representa una agrupación no ordenada de datos. El número de elementos es fijo. Cada uno de los datos de la agrupación puede tener un tipo distinto. A los datos se accede generalmente por un identificador. Secuencia Representa una agrupación ordenada de datos. La longitud de la secuencia puede ser fija o variable. Todos los elementos de la secuencia tienen el mismo tipo. A los datos se accede mediante su posición. Referencia Representa un enlace a un dato, no el dato propiamente dicho. Se usa para compartir el mismo dato entre varios puntos de la estructura de datos. Alternativa Representa a un dato. El dato puede ser de uno de los tipos indicados en la alternativa. Hemos observado que básicamente todos los lenguajes “estudiados” comparten las mismas primitivas. Existe un gran parecido entre ellos, pero permanece oculto por la diferente sintaxis y los distintos puntos de vista con los que se estudia cada lenguaje y cada paradigma. 3. Representaciones Gráficas Para un mejor entendimiento de las primitivas anteriormente expuestas, vamos a proponer una representación gráfica para cada una de ellas. Átomo / Tipo Simple Podemos representar un tipo simple poniendo su nombre dentro de un rectángulo. Aquí mostramos los tipos entero y decimal: 21 Sobre la información, su estructura y su-gestión entero decimal Un dato cuyos valores sean de tipo simple se puede representar dentro de una forma rectangular con las esquinas redondeadas. Aquí mostramos algunos datos con valores 4, 7, 3.2, 89.3: 4 7 3.2 89.3 Para representar en un mismo gráfico un tipo y aquellos datos que tienen ese tipo, los unimos con una línea terminada en círculo. En este gráfico podemos ver las dos representaciones anteriores juntas: entero 4 decimal 7 3.2 89.3 En algunas estructuras de datos que tengan muchos datos simples, puede ser bastante engorroso unir todos los datos con su respectivo tipo mediante una línea. Para poder indicar el tipo de un dato sin necesidad de unirse a él mediante una línea se puede usar la siguiente representación: entero decimal 4 3.2 Registro Para representar un tipo registro dibujamos un rectángulo y dentro el nombre del tipo. Cada uno de los campos de un registro viene determinado 22 Sobre la información, su estructura y su-gestión por un nombre y un tipo. Para representar gráficamente un campo, unimos el rectángulo del tipo registro con el rectángulo del tipo del campo con una línea terminada en rombo. El nombre del campo aparecerá sobre la línea de unión. Mostramos aquí la representación gráfica de un registro llamado tPersona con tres campos: nombre tPersona apellido telefono entero Como podemos observar, el tipo cadena es usado en dos campos. Puesto que hay un único tipo llamado cadena, solo aparece un rectángulo que lo representa. Sin embargo, cuando el dibujo sea muy lioso, se permite duplicar la representación de un tipo. En la siguiente figura mostramos el mismo esquema duplicando el tipo cadena. nombre tPersona apellido telefono cadena cadena entero Hay que destacar que no existen dos tipos cadena. Sólo existe un tipo cadena. La duplicación del elemento gráfico es por mejorar la legibilidad de la representación. Si el tipo cadena fuese a su vez un registro, sólo sería necesario indicar los campos en uno de los cuadrados. Hay veces en que esta representación gráfica es demasiado detallada. Se necesita una forma compacta de poder representar gráficamente un tipo basado en registro. La representación elegida será la siguiente: 23 Sobre la información, su estructura y su-gestión tPersona nombre : cadena apellido : cadena teléfono : entero Para representar datos que tengan como tipo un registro también existe una representación. El dato se representa por un cuadrado vacío y unido a él se representan los valores de cada uno de sus campos. Un dato que represente la información de una persona sería: nombre tPersona apellido telefono nombre apellido entero Pedro "Sánchez" telefono 670456534 En este gráfico existe información redundante. Los datos se asocian a su tipo directamente, por ejemplo “Pedro” con el tipo cadena. Por otro lado los datos se asocian a su tipo a través del nombre con el que se asocian al dato registro. En algunas ocasiones, para mejorar la legibilidad, se permite omitir la relación explícita entre dato y su tipo siempre que esta relación se pueda inferir a través de un tipo compuesto. Con esta simplificación el dato anterior junto con su tipo quedaría: 24 Sobre la información, su estructura y su-gestión nombre tPersona apellido telefono nombre apellido entero Pedro "Sánchez" telefono 670456534 Al igual que existe una representación resumida para el tipo registro, también existe una representación resumida para los datos de ese tipo. La representación resumida se crea metiendo los datos que componen el registro dentro del cuadrado que representa el dato. Una representación con el tipo y el dato juntos sería: tPersona nombre : cadena apellido : cadena teléfono : entero nombre = "Pedro" apellido = "Sánchez" teléfono = 670456534 Es importante destacar que las representaciones resumidas de datos se pueden combinar con representaciones completas de tipos y viceversa. Al igual que con los datos de tipos simples, los datos de tipos registro también se pueden representar colocando el nombre del tipo en la parte 25 Sobre la información, su estructura y su-gestión superior del cuadrado redondeado. Una representación equivalente a la anterior sería: tPersona nombre : cadena apellido : cadena teléfono : entero tPersona nombre = "Pedro" apellido = "Sánchez" teléfono = 670456534 Secuencia Para representar un tipo secuencia creamos un rectángulo con el nombre del tipo dentro. Unimos mediante una línea terminada en rombo el cuadrado al tipo de los elementos que contendrá la secuencia. Además, acompañamos el rombo con un número que indique el número de elementos que contendrá la secuencia. Si queremos indicar que el número de elementos no está determinado en el tipo usamos un asterisco. La representación de un tipo array de cinco de enteros sería: 5 tSecEnteros entero La representación de un tipo array con un número indeterminado de enteros sería: * tSecEnteros 26 entero Sobre la información, su estructura y su-gestión Para representar un dato de este tipo junto con el tipo la representación usada es la siguiente: tSecEnteros * entero [1] 4 [2] 5 [3] 7 Los números que acompañan a los datos individuales se han puesto para mejorar la legibilidad del gráfico, pero pueden omitirse. Al igual que en los registros, no es necesario tampoco especificar explícitamente la relación entre los datos del array y su tipo, ya que esta relación está implícita en la pertenencia al dato array. Un gráfico con las dos simplificaciones sería: * tSecEnteros entero 4 5 7 También existen representaciones resumidas para el tipo y para los valores. En el siguiente gráfico mostramos la definición del tipo junto con dos datos pertenecientes al tipo. 27 Sobre la información, su estructura y su-gestión tSecEnteros * : entero 4,5,7 4 5 7 Nótese que existen dos formas de resumir los datos de tipo array. Se pueden colocar los valores horizontalmente separados por comas o verticalmente separados por un espacio. Referencia El tipo referencia se representa mediante un rectángulo (como el resto de los tipos) unido al rectángulo del tipo al que referencia. La unión se crea mediante una línea con punta de flecha lineal apuntando en el sentido del tipo referenciado. Una tipo referencia a entero sería: entero tRefEnt La representación de un dato apuntando a otro dato de tipo entero sería: pEntero entero 4 28 Sobre la información, su estructura y su-gestión Podemos ver como el dato que tiene albergado un 4 y es de tipo entero se asocia a su tipo por dos caminos. Por un lado directamente y por otro a traves del dato puntero. En este caso también se puede hacer la simplificación hecha anteriormente para definir los tipos explícitamente. El gráfico con esa simplificación quedaría: pEntero entero 4 Para crear una representación simplificada del tipo se puede usar el siguiente la siguiente representación: ^entero Con los demás mecanismos usábamos simplificaciones también en los datos del tipo determinado, en este caso no podemos hacerlo. Una referencia se crea para crear un enlace entre dos datos de la misma estructura. Este enlace se crea para no tener duplicada la información en dos lugares de la estructura de datos. Por este motivo siempre que se represente una referencia hay que dibujar una flecha desde el punto donde esté la referencia hasta el punto donde esté el dato referenciado. Alternativa Para representar una alternativa se crea una caja con el nombre del tipo de la alternativa. Esta caja se une a los tipos que forman la alternativa mediante una 29 Sobre la información, su estructura y su-gestión línea que “comienza” en punta rellena. Para representar el tipo alternativa AltNumero que alberga a enteros y reales se representaría: real AltNumero entero La representación del tipo alternativa se puede hacer de forma resumida. La representación del ejemplo anterior sería: tAltNumero entero | real Para representar la relación con los datos que pertenecen a ese tipo se representaría: real AltNumero entero 4 Otro valor se podría representar: 30 Sobre la información, su estructura y su-gestión real AltNumero entero 4.5 En este caso no se pueden hacer simplificaciones a la hora de mostrar la relación del tipo con el dato. Antes simplificábamos porque la información era redundante, ahora el tipo interno de un dato de tipo alternativa puede ser elegido entre mas tipos, por tanto es necesario indicarlo explícitamente. Para representar la pertenencia de un dato a un tipo concreto se puede usar la representación que coloca el nombre del tipo en la parte superior del dato. Una representación equivalente a la anterior sería: AltNumero decimal 4.5 entero 4 Unión de mecanismos En la mayoría de las ocasiones necesitaremos usar todos los mecanismos vistos de manera conjunta. Si queremos representar una secuencia de tamaño 31 Sobre la información, su estructura y su-gestión ilimitado de registros para representar personas lo representaremos de la siguiente forma: nombre * tSecPersonas apellido tPersona telefono entero Resumiendo el tipo registro sería: tPersona * tSecPersonas nombre : cadena apellido : cadena teléfono : entero Resumiendo los dos tipos tendríamos la siguiente representación: tSecPersonas * : tPersona nombre : cadena apellido : cadena teléfono : entero Algunos ejemplos de representación de datos de este tipo compuesto de forma detallada serían: 32 Sobre la información, su estructura y su-gestión nombre * tSecPersonas apellido tPersona telefono entero nombre [1] "Pedro" apellido "Sánchez" telefono 670456534 nombre [2] "Alberto" apellido "Arevalillo" telefono 670236534 nombre [3] "Juan" apellido "Pérez" telefono 670411534 La misma representación se puede hacer de forma resumida de la siguiente forma: 33 Sobre la información, su estructura y su-gestión tSecPersonas * : tPersona nombre : cadena apellido : cadena teléfono : entero nombre = "Pedro" apellido = "Sánchez" teléfono = 670456534 nombre = "Alberto" apellido = "Arevalillo" teléfono = 670236534 nombre = "Juan" apellido = "Pérez" teléfono = 670411534 Simplificaciones en la unión de mecanismos La forma de representar una secuencia de referencias a un registro de tipo persona sería: nombre tSecRefPersona * tRefPersona tPersona apellido telefono entero En algunas ocasiones definir explícitamente el tipo tRefPersona es engorroso. Por este motivo se permite poner el asterisco en la flecha de relación. El siguiente gráfico sería equivalente al anterior: nombre tSecRefPersona * tPersona apellido telefono 34 entero Sobre la información, su estructura y su-gestión Si queremos representar un registro que tiene una secuencia entre sus atributos lo representaremos de la siguiente manera: nombre apellido tPersona numerosTlf tSecEnteros * entero Para representar que un campo es una secuencia de datos se puede hacer la siguiente representación: nombre tPersona apellido numerosTlf * entero Ambas simplificaciones se pueden usar juntas si lo que queremos representar es que un registro tengan como campo una secuencia de referencias. Por ejemplo, el siguiente esquema nuestra los datos de una persona y las direcciones son referencias ya que varias personas pueden compartir la misma vivienda. 35 Sobre la información, su estructura y su-gestión tSecPersonas nombre * apellido tPersona numerosTlf direcciones * calle entero * ciudad tDireccion número entero Un dato de ejemplo para esta estructura de datos sería: tSecPersonas tPersona [1] nombre = "Pedro" apellido = "Sánchez" teléfonos = 670456534, 925761515 direcciones = , tDirección calle = "Tejar" ciudad = "Torrijos" número = 7 tDirección calle = "Puente" ciudad = "La Pueba" número = 13 tPersona [2] nombre = "Alberto" apellido = "Arevalillo" teléfonos = 670236534 direcciones = tPersona [3] nombre = "Juan" apellido = "Pérez" teléfonos = direcciones = 4. Mecanismos de construcción de estructuras de datos de otros sistemas de representación de la información Para hacer nuestro “estudio” hemos elegido varios lenguajes de programación de distintos paradigmas. En el mundo de la informática hay 36 Sobre la información, su estructura y su-gestión otros sistemas en los cuales hay que crear estructuras de datos. Nos referimos, entre otros, a las bases de datos relacionales y al XML. Las bases de datos relacionales persiguen la persistencia de la información y su rápida consulta y modificación. XML persigue la transmisión de información jerarquizada entre sistemas heterogéneos. Vamos a hacer un breve repaso de los mecanismos que tienen para construir sus estructuras de datos. Veremos si tienen alguna relación con los cinco mecanismos básicos que hemos encontrados en los lenguajes de programación. Bases de datos relacionales Tipo Atómico Para representar la información en una base de datos se dispone de los tipos atómicos mas comunes. Los tipos atómicos permiten representar enteros, cadenas de caracteres, reales, lógicos, etc... Registro En una base de datos relacional se pueden crear registros. De hecho en una base de datos existe el concepto de registro de la misma forma que el que hemos visto aquí. Los registros son agrupaciones de datos, con el número de datos fijo. Cada uno de los datos que forman un registro se llama campo. Los campos se referencian por su nombre. Secuencia Las secuencias de elementos del mismo tipo también se permiten. Estas secuencias se conocen como tablas. El número de elementos de las tablas es variable. Curiosamente, en las bases de datos relacionales teóricas, los datos dentro de la tabla no están ordenados y por tanto no son accesibles mediante un índice. Para acceder a los datos de las tablas se tienen en cuenta los valores de los campos. 37 Sobre la información, su estructura y su-gestión Referencia En las bases de datos existe una forma primitiva de referencia. Es el usuario el que tiene que manejar en gran parte las referencias. La forma en que se crean enlaces es similar que la que hemos visto en Lisp. El usuario tiene que crear un campo en el registro cuyo valor identifique al registro entre todos los demás, la llamada clave primaria. Cuando desea crear una referencia a datos de ese registro, tiene que crear un campo denominado clave foránea. Alternativa En una base de datos relacional no hay nada parecido a la alternativa. Hasta hace muy poco tiempo la alternativa no se consideraba esencial. Por ese motivo y para mejorar la eficiencia no se ha incluido en las bases de datos relacionales. Actualmente, las bases de datos derivadas de las relacionales, denominadas objeto-relacionales, incorporan mecanismos propios de orientación a objetos que permiten hacer uso de la alternativa. Combinación de Mecanismos Una base de datos relacional tiene una estructura muy rígida. El anidamiento de mecanismos está muy restringido. El esquema de cualquier base de datos relacional es el siguiente: 38 Sobre la información, su estructura y su-gestión nombreBaseDeDatos nombreCampo1Tabla1 nombreTabla1 nombreCampo2Tabla1 nombreCampoNTabla1 nombreCampo1Tabla2 nombreTabla2 nombreCampo2Tabla2 nombreCampoNTabla2 nombreCampo1TablaN nombreTablaN nombreCampo2TablaN nombreCampoNTablaN tipoAtómico tipoAtómico tipoAtómico tipoAtómico tipoAtómico tipoAtómico tipoAtómico tipoAtómico tipoAtómico Siendo tipoAtómico cualquiera de los tipos atómicos que se permiten en una base de datos relacional. Pese a las limitaciones observadas, vemos como una base de datos relacional tiene la gran mayoría de los mecanismos vistos en los lenguajes de programación. Es sorprendente que sean tan parecidos unos sistemas y otros y que no se expliquen en los mismos términos. XML basado en XML Schema La definición a la que se tiene que acoger un documento XML se puede expresar mediante una DTD, Document Type Definition o bien mediante un XML Schema. El XML Schema es mucho mas potente en cuanto a especificación que la DTD. Vamos a “estudiar” los mecanismos existentes en XML Schema. 39 Sobre la información, su estructura y su-gestión Tipos Atómicos En un documento XML se pueden tener datos de los tipos primitivos mas importantes. XML dispone de muchos tipos primitivos, pero los básicos son los enteros, reales, lógicos y cadena de caracteres. Registro Es el llamado tipo compuesto. Los campos que forman un registro en XML se dividen en dos grupos, los atributos y los elementos. Los tipos y mecanismos usados en los atributos son un subconjunto de los usados en los elementos. La funcionalidad básica es similar a la vista hasta ahora en los demás registros. Secuencia Mediante unos valores de número mínimo de ocurrencias y número máximo de ocurrencias se pueden tener secuencias de longitud fija o secuencias de longitud variable. La funcionalidad es similar a la vista en los lenguajes de programación. El acceso a los elementos es bastante potente, pudiéndose acceder a ellos por posición, como en los lenguajes de programación y mediante los valores de los datos, como en las bases de datos. Referencia La referencia esta también soportada en XML. Mediante los mecanismos key y keyRef se pueden crear referencias. Además una referencia puede establecerse dinámicamente dependiendo del valor de los registros. Alternativa XML soporta la creación de tipos de forma similar a la programación orientada a objetos. Por tanto, con herencia y polimorfismo se consigue la funcionalidad de la alternativa, pero de forma mucho mas avanzada. Combinación de Elementos XML es muy potente en este aspecto. De hecho el registro y la secuencia se pueden combinar de una manera muy eficaz y rápida para crear estructuras 40 Sobre la información, su estructura y su-gestión muy complejas. Además, el soporte avanzado de referencias que posee es muy potente a la hora de gestionar datos muy interrelacionados. Los niveles de anidamiento en los elementos de un tipo compuesto, lo que nosotros conocemos como campos, es ilimitado. En XML aparecen todos los mecanismos vistos hasta ahora. Parece que estos mecanismos se encuentran en todos aquellos lugares donde hay que definir la estructura de los datos. 5. Mecanismos de construcción de la estructura de las sentencias en los lenguajes de programación Según Cox "el software son los datos que se suministra el programador en tiempo de compilación para manipular los datos del usuario en tiempo de ejecución". Si los códigos fuente son datos, ¿cómo se define la estructura de esos datos? Los mecanismos que definen la estructura de un código fuente ¿son los mismos que los que definen las estructuras de datos que manejan esos programas? Cabe preguntarse si los programas, como datos, respetan las reglas de construcción de datos expuestos hasta el momento: la sentencia de asignación puede entenderse como una orden de ejecución atómica. La sentencia compuesta puede entenderse como un registro de ordenes de ejecución de diversa naturaleza formando un todo. La sentencias iterativas pueden entenderse como una secuencia de ordenes de ejecución. Las sentencias alternativas pueden entenderse como una orden de ejecución u otra dependiendo de cierta condición. La llamada a un subprograma puede entenderse como la referencia a una orden de ejecución. Procedemos a su estudio: 41 Sobre la información, su estructura y su-gestión Se pueden agrupar elementos atómicos en los lenguajes de programación, es decir, se crean grupos de instrucciones que se tratan formando un todo, por ejemplo en Pascal, todas aquellas instrucciones que vayan entre BEGIN y END se tratan como una instrucción compuesta. De igual forma, en cuanto a los datos podemos crear registros, unidades que tratan como un todo a un conjunto de variables. En esta forma de agrupar elementos hay que especificar cada uno de los elementos que forman el grupo, de tal forma, que esos elementos son potencialmente de naturaleza distinta. En cambio, existe otra forma de agrupar elementos, en la cual, los elementos han de ser de la misma naturaleza. En cuanto a datos nos estamos refiriendo a un array, es decir, un conjunto de variables del mismo tipo. La forma de definir estas agrupaciones de datos es indicando el tipo de un elemento, y el número de estos elementos que deseamos tener. En código se observa una cosa parecida, nos referimos a los bucles, el caso mas sencillo, en un bucle for con una instrucción, lo que tenemos conceptualmente es una instrucción repetida tantas veces como lo que se indique en el bucle for. En muchos casos, la instrucción que contiene un bucle es una instrucción compuesta, de igual forma, se podría construir un array de registros. El otro elemento clave en la estructuración tanto de datos como de la ejecución de instrucciones es la alternativa. En el caso de la ejecución de las instrucciones, la alternativa viene representada por la sentencia condicional if. Esta sentencia alberga a otras dos instrucciones, que de forma general pueden ser compuestas, y cuya semántica es que no se pueden ejecutar ambas instrucciones, sino solamente una de ellas. En cuanto a la estructura de los datos, tenemos los llamados registros variantes, registros en los cuales se definen dos posibles “estructuras”, las variables que tengan este tipo variante, solo pueden tener unos datos, o bien con una estructura o bien con la otra, pero no con ambas. La manera de determinar que alternativa es la que se hace efectiva (se ejecuta en código o indica la estructura de los datos) depende de la naturaleza del 42 Sobre la información, su estructura y su-gestión elemento del que hablemos. En código ejecutable, es decir, en la instrucción if se determina que instrucción ejecutar dependiendo del resultado de evaluar una expresión, es decir, del resultado de ejecutar un código. En cambio en cuanto a datos, la manera de elegir la estructura que va a tener posteriormente una variable con ese tipo es el valor de un campo común que han de tener las dos alternativas. Esta proyección entre la estructura de datos y estructura del código fue preconizada por Warnier y Jackson que establecían que la estructura de datos del programa determinaba la estructura del sentencias del programa. En textos docentes actuales sobre la programación permanece explícitamente la relación entre los arrays y la sentencia for, el registro variante y la sentencia case, etc. 6. Mecanismos de construcción de los lenguajes de programación No hay ninguna duda de que un código fuente de un programa es un dato compuesto. Cada uno de los códigos fuente de un lenguaje de programación se tienen que acoger a unas reglas, a una definición para considerarse correctos.Todos los lenguajes de programación obligan a que sus códigos fuente estén definidos de forma textual. El mecanismo usado para especificar una estructura en una secuencia de caracteres se llama gramática. Pese a que hay dos gramáticas usadas para definir un lenguaje, las de tipo 3 y las de tipo 2. Sabemos que se pueden especificar todas las reglas textuales de un lenguaje usando una gramática de tipo 2. La forma más común de escribir en gramática tipo 2 es mediante la forma EBNF. Por tanto, vamos a estudiar los mecanismos que permite EBNF para definir estructuras. ¿ Aparecerán los cinco mecanismos vistos hasta ahora? Comprobemos su proyección: • los elementos atómicos son los terminales de la gramática; 43 Sobre la información, su estructura y su-gestión • la producción "registra" conjuntamente elementos de diversa naturaleza de la gramática; • los operadores de repetición '+' y '*' determinan las secuencias de elementos de la misma naturaleza; • el operador de disyunción '|' determina la alternatividad entre dos elementos de la gramática; • el no terminal da la "referencia" a otras producciones; Parece que con elementos atómicos, y con la posibilidad de agruparlos de forma homogénea o heterogénea, la capacidad alternativa y las referencias hemos modelizado las instrucciones ejecutables y los datos en un lenguaje de programación estructurado como es Pascal. Volvemos a encontrarnos estas cinco formas de relacionar elementos atómicos en las gramáticas, mecanismo formal para definir los lenguajes. Los elementos atómicos son los tokens cuando hablamos de gramáticas que definen las características sintácticas de un lenguaje o bien caracteres cuando la gramática define las características léxicas. Tenemos también agrupación de elementos heterogéneos, es decir, que pueden no ser iguales, y que se tratan como una unidad. Es el caso de los no terminales. Básicamente los no terminales son símbolos que representan la agrupación de otros símbolos que a su vez pueden ser o no terminales. De igual forma se puede representar la alternativa, mediante eso, la alternativa de las gramáticas y representa que ante un mismo símbolo no terminal, se está representando un elemento u otro, pero no ambos a la vez. Y por último nos queda la representación de elementos homogéneos, es decir, la repetición de elementos. En las gramáticas, se representa la repetición mediante la recursividad en la definición de elementos y la alternativa para ofrecer el caso base. De ese modo se pueden definir conjuntos o secuencias de elementos homogéneos. 44 Sobre la información, su estructura y su-gestión 7. Axiomática de la información Llegamos a la conclusión de que de igual forma que se modelan los datos, podemos modelar el código e incluso las reglas con las que se define ese código. Este aspecto nos conduce a la posible unificación de notaciones sintácticas para aspectos tan dispares como la definición de datos, de sentencias y de gramáticas con un lenguaje único. Por ejemplo, la siguiente definción de Pascal TYPE Intervalo = RECORD minimo: Integer; maximo: Integer; END; podría reescribirse con un lenguaje universal Intervalo REFERENCE CLUSTER minimo: Integer; maximo: Integer; END CLUSTER También, la siguiente definción de Pascal PROCEDURE Inicializar; BEGIN x:=0; y:=0; END; podría reescribirse con un lenguaje universal Inicializar REFERENCE CLUSTER x:=0; y:=0; END CLUSTER Y, finalmente, parte de la gramática de Pascal Suprograma ::= Cabecera Cuerpo podría reescribirse en un lenguaje universal 45 Sobre la información, su estructura y su-gestión Suprograma REFERENCE CLUSTER Cabecera Cuerpo END CLUSTER En este punto cabe reflexionar sobre la naturaleza de los cinco mecanismos ¿son primitivos? ¿son ampliables? Parece extraño la presencia de secuencia y registro frente a la ausencia de estructuras jerárquicas como árboles o grafos, aplicaciones y conjuntos entre otros. Rápidamente se puede observar que estas propuestas con fácilmente soportadas por los conceptos de átomos y referencias dispuestas según la estructura particular. Luego, si cabe, los cinco mecanismos son reducibles más que ampliables. De hecho una secuencia puede concebirse como un átomo con un conjunto de referencias a otros átomos, sus elementos, que a su vez se referencian entre sí para determinar un orden total. Por otro lado, un registro puede concebirse como un átomo con un conjunto de referencias a sus campos sin establecer un orden entre ellos. En ambos casos, para la secuencia y la alternativa en datos, sentencias o gramáticas, las referencias a sus elementos en su definición y su concreción (p.e. un tipo registro y una variable de ese tipo registro) mantiene la misma cardinalidad de relaciones con sus elementos (p.e. el tipo registro tiene un número de campos y una variable de ese tipo registro tiene el mismo número de campos). Por contra, la definición de la alternativa (p.e. un tipo registro variante) mantiene un número de relaciones con sus elementos alternativos y su concreción (p.e. una variable de ese tipo registro) alberga una única relación con el elemento escogido. Esta situación se repite en el código estático de la sentencia compuesta (mecanismo de registro) con sus sentencias y la ejecución dinámica de la sentencia compuesta y la ejecución de todas sus sentencias; frente al código estático de la sentencia alternativa (mecanismo alternativo) con sus ramas y la ejecución dinámica de la sentencia y la ejecución de una de sus ramas. 46 Sobre la información, su estructura y su-gestión Concluyendo, los 5 mecanismos de la estructura de la información pueden quedar resumidos con 2 primitivas (átomo y referencia) y tres patrones recurrentes (secuencia, registro y alternativa) construidos a partir de estos dos mecanismos primitivos. Quizás esta extrema simplificación asiente una posible formalización de la estructura de la información a partir de átomos y sus relaciones en un espacio n-dimensional. El próximo capítulo, persigue un objetivo similar pero menos ambicioso: modelar léxico, sintáctica y semánticamente el lenguaje pascal, los códigos de cualquier programa en Pascal y la ejecución de éstos mediante una misma técnica: la tecnología orientada a objetos restringida a objetos (átomos) y punteros (referencias). 47 III. Modelado de Pascal 1. Introducción Queremos construir un sistema que represente los concetptos de la programación con lenguaje Pascal usando la tecnología orientada a objetos. Hay que dejar claro en qué grado nuestro programa va a ser orientado a objetos, ya que todos sabemos que un programa escrito en un lenguaje orientado a objetos, como Java, no tiene por que estar necesariamente orientado a objetos. Vamos a usar la tecnología orientada a objetos principalmente para modelar Pascal. En otras palabras, queremos representar un código fuente escrito en Pascal como una estructura de objetos en memoria. Una vez que tengamos en memoria esos objetos, los interpretaremos para realizar las mismas acciones que si se ejecutase dicho programa. 48 Sobre la información, su estructura y su-gestión Como se puede ver, existen dos partes muy diferenciadas en el nuestro modelado: - Tiempo de compilación Tiempo de ejecución La parte encargada de la compilación generará una serie de objetos partiendo de un código fuente. Posteriormente veremos con todo detalle tanto el proceso de modelado de las clases que representan el lenguaje Pascal como la creación de las instancias de dichas clases para representar un código fuente en memoria. La parte encargada de la ejecución interpretará esa estructura de objetos para representar los conceptos y acciones que ocurren cuando se ejecuta dicho código fuente. La idea principal en la que nos basamos para construir el intérprete es la relación que existe en programa y proceso. Si un programa dicta las reglas para que se ejecute un proceso, un programa actúa de definición y el proceso actúa como particularización. Llevado esto a la tecnología orientada a objetos, las definiciones se representan por clases y las materializaciones se representan por instancias. Por tanto, el objetivo es que a partir de cada elemento de un código fuente se cree una clase que le represente, y cada vez que ese elemento se vea reflejado en la ejecución, se creará una instancia de dicha clase. Más adelante buscaremos la forma de llevar a cabo esta idea. 2. Tiempo de Compilación Introducción Para estudiar como podríamos modelar un código fuente en Pascal comenzaremos con uno muy sencillo. A grandes rasgos veremos como se podría representar como objetos. Iremos añadiendo cosas a nuestro programa sencillo y veremos como va aumentando el número de objetos y el modelo se 49 Sobre la información, su estructura y su-gestión hace mas completo. Cuando hayamos cogido un poco de soltura en la representación de los elementos de un programa, comenzaremos un estudio más riguroso de la jerarquía de clases necesaria para representar cualquier programa en Pascal. Partiremos del siguiente programa escrito en Pascal: PROGRAM EJEMPLO; VAR I:INTEGER; BEGIN I := 4; END. A grandes rasgos y sin especificar las clases de cada uno de los objetos, la representación en memoria del programa anterior sería: 50 Sobre la información, su estructura y su-gestión programa Ejemplo declaracion Variable nombre tipo “integer” “EJEMPLO” instruccion 1 “I” Asignacion I:=4 referencia al Tipo Referenc Tipo Entero nombre Cuerpo programa Ejemplo Declaración Variable I definicion nombre variable variable Definicion Variable I cuerpo parte Derecha parte Izquierda Valor4 Referencia Variable I referencia a la Variable “I” Parece que tendremos una clase para representar el programa, sus atributos serán el nombre, la variable y la instrucción. Vemos que los nombres se pueden representar como cadenas de caracteres. También tendremos que tener una clase para representar la declaración de una variable, y sus atributos serán el nombre y el tipo. Otra clase representaría la asignación, con un nombre de variable y el valor. Si añadimos una variable más y una instrucción más al programa, teniendo por ejemplo: 51 Sobre la información, su estructura y su-gestión PROGRAM EJEMPLO; VAR I:INTEGER; J:CHAR; BEGIN I := 4; J := ‘a’; END. Vemos que los atributos que teníamos en la clase para representar un programa se convierten en arrays o secuencias, ya que en un programa pueden existir muchas instrucciones y también muchas declaraciones de variables. La representación en memoria del programa sería: programa Ejemplo declaracion Variable 1 declaracion Variable 2 nombre cuerpo nombre variable Declaración Declaración Variable Variable “I” I J definicion definicion variable variable nombre variable Definicion Definicion Variable Variable “J” I J referencia al Tipo Referenc Tipo Entero referencia al Tipo Referenc Tipo Caracter Cuerpo programa Ejemplo “EJEMPLO” instruccion 1 instruccion 2 Asignacion I:=4 Asignacion J:=’a’ parte Derecha parte Izquierda parte Derecha parte Izquierda Valor4 nombre tipo “integer” Valor ‘a’ nombre tipo “char” Referencia Variable I referencia a la Variable “I” Referencia Variable J referencia a la Variable “J” Vamos a añadir una sentencia condicional a nuestro programa. Quedaría: 52 Sobre la información, su estructura y su-gestión PROGRAM EJEMPLO; VAR I:INTEGER; J:CHAR; BEGIN IF TRUE THEN I := 4 ELSE J := ‘a’; END. Nuestra estructura de objetos quedaría por tanto: programa Ejemplo declaracion Variable 1 declaracion Variable 2 nombre cuerpo nombre variable Declaración Declaración Variable Variable “I” I J definicion definicion variable variable nombre variable Definicion Definicion Variable Variable “J” I J referencia al Tipo Referenc Tipo Caracter nombre tipo nombre tipo “EJEMPLO” instruccion 1 Sentencia Condicional referencia al Tipo Referenc Tipo Entero “integer” Cuerpo programa Ejemplo parte Evaluable parte THEN Asignacion I:=4 Asignacion J:=’a’ parte Derecha “char” parte Izquierda parte Derecha parte Izquierda Valor4 Referencia Variable I referencia a la Variable “I” 53 Valor TRUE parte ELSE Valor ‘a’ Referencia Variable J referencia a la Variable “J” Sobre la información, su estructura y su-gestión Una clase para representar la sentencia condicional de nuestro ejemplo tendría tres atributos, uno de ellos para representar el valor, otro para representar la parte THEN y el último para representar la parte ELSE. Vemos que los atributos para la parte THEN y para la parte ELSE serían una secuencia compuesta, ya que pueden existir varias instrucciones en cada parte. Podemos observar que la sentencia condicional y la sentencia de asignación tienen que colocarse en la misma secuencia, al fin y al cabo, las dos son instrucciones. Por tanto, parece razonable crear una clase padre para ambas o un interfaz que las dos implementen y que sean tratadas de forma polimórfica. Vemos que la cosa se complica por momentos. Indicar aquí toda la evolución desde estos primeros bocetos hasta dar con una jerarquía de clases adecuada podría aburrir al lector. Pasaremos por tanto a estudiar la jerarquía de clases propuesta para modelar el lenguaje Pascal, es decir, que las instancias de esas clases representarán un código fuente concreto. Modelado del Lenguaje Pascal Introducción El problema fundamental que tenemos a la hora de modelar un lenguaje de programación es que tenemos que tener muy claro que representa cada concepto. Por tanto, antes de poder indicar que como es la clase que representa un concepto, tenemos que dar una breve descripción de lo que para nosotros representa ese concepto. Esto es importantísimo, ya que un mismo concepto, dependiendo desde el punto de vista desde donde se mire, podría estar representado por una clase u otra totalmente distinta. Otra cosa que hay que comentar antes de comenzar a modelar es la falta de ortogonalidad. Por ejemplo, se pueden hacer una llamada a un subprograma 54 Sobre la información, su estructura y su-gestión dentro del propio subprograma para hacer llamadas recursivas. En cambio esto no es posible cuando se trata del principal. Este tipo de excepciones en la estructura general no serán contempladas en el modelado. Ya veremos como se controlarán en sucesivos capítulos. Vamos a hacer un breve repaso de los conceptos más generales que aparecen en Pascal. Éstos son el concepto de evaluable, de sentencia, de declaración, de definición, de referencia, de elementos predefinidos, de ámbito, y los conceptos de tipos y valores. Posteriormente haremos un repaso un poco más detallado de otros elementos que aparecen en un código fuente. Evaluable Consideramos elementos evaluables todos aquellos de los que se podrá obtener un valor en tiempo de ejecución. En Pascal son muy dispares los elementos de los que se puede obtener un valor en tiempo de ejecución. Un valor, por ejemplo el 5, es evaluable, ya que en tiempo de ejecución él será su propia evaluación. La llamada a una función también es evaluable, ya que cuando se ejecute devolverá un valor. Las expresiones son evaluables, por ejemplo, la expresión constante 4 + 92 es evaluable, en ejecución se evaluará con el valor 96. Las variables también son evaluables, ya que en tiempo de ejecución, tendrán un valor asociado y ese valor será el que se obtendrá de ellas al evaluarlas. Por último nos quedan las constantes, la constante MAXINT de Pascal, en tiempo de ejecución se evaluará como 32.676. Para que no existan dudas, veremos algunas cosas que no son evaluables. Por ejemplo en Pascal una asignación no es evaluable, porque en tiempo de ejecución no se obtiene un valor de ella. Hay que tener claro esto, porque en C una asignación sí que es evaluable. Una declaración de una variable no es evaluable. Una declaración de variable es la parte del código en la que se indica 55 Sobre la información, su estructura y su-gestión el tipo y el nombre de una variable, pero de ese elemento del código no se obtiene ningún valor en tiempo de ejecución. Sentencia Consideramos sentencias aquellos que pueden aparecer solos en una línea del cuerpo de un programa o de un subprograma. Las sentencias de Pascal son la asignación, la llamada a un procedimiento y las sentencias de control de flujo de ejecución, es decir, if, for, while, repeat y case. La sentencia with, obviamente, también es una sentencia. De igual forma vamos a ver los elementos que no consideramos sentencias. Por ejemplo, la declaración de una variable no se considera una sentencia. En otros lenguajes como Java, una asignación se consideraría una sentencia, ya que se pueden declarar variables en medio de otras sentencias. La definición de un subprograma no es una sentencia, ya que no se puede declarar un subprograma en el cuerpo de otro subprograma. Declaración Se considera una declaración a cualquier elemento del código fuente que asigne un nombre a otro elemento del código, al que llamaremos el elemento definido, y que permita usar el elemento definido en el ámbito donde se encuentre esa declaración. Por ejemplo, una declaración de una variable es una declaración. Podemos ver que una declaración de una variable asocia el nombre de la variable a un tipo. Una declaración de constante es también una declaración, porque asocia el nombre a un valor. Una declaración de tipo también es una declaración, ya que asocia un nombre a un tipo, ya sea primitivo o no. Lo mismo ocurre con la declaración de un subprograma, son declaraciones porque asocian un nombre a la definición de un subprograma que consta de parámetros, cuerpo, etc... Por último una declaración de un 56 Sobre la información, su estructura y su-gestión parámetro también es una declaración, ya que asocia un nombre a un tipo y a una posición determinada de la lista de parámetros. Un programa principal también es una declaración, porque se asigna un nombre a la definición del programa formada por subprogramas, variables, etc. Hay que tener en cuenta, que no se permite referenciar al propio programa en su propio ámbito, lo que sin duda es una falta de ortogonalidad del lenguaje Pascal, pero como hemos comentado al principio, ese tipo de situaciones no se consideran en el modelado. Definición Todo aquel elemento del código fuente que define un nuevo elemento. Elementos que se pueden definir son nuevos tipos, nuevos procedimientos, nuevas variables, nuevas constantes, etc... Existen definiciones que pueden existir en un código fuente sin aparecer formando parte de una declaración. Por ejemplo, existen los tipos anónimos, son nuevos tipos pero que no tienen un nombre asociado. Aunque la mayoría de los elementos que se definen son nombrados y por tanto aparecen formando parte de una declaración. Por ejemplo las variables, constantes, procedimientos, funciones, etc... Elementos predefinidos En todos los lenguajes de programación existen elementos predefinidos. Son elementos declarados, con un nombre asociado, que pueden ser usados en cualquier parte del programa. Las declaraciones no aparecen en el código fuente, pero conceptualmente son declaraciones, ya que asocian un nombre a elementos que pueden ser usados en cualquier parte del programa. Referencia Consideramos referencia al uso de un elemento declarado. Por ejemplo, el nombre de una variable en una sentencia de asignación es una referencia a la variable previamente declarada. La llamada a un procedimiento, es decir, el 57 Sobre la información, su estructura y su-gestión nombre del procedimiento junto con sus parámetros actuales, si tiene, sería una referencia al procedimiento previamente declarado. El uso de una constante en la parte derecha de una asignación también es una referencia a la constante previamente declarada. Cuando ponemos el nombre de un tipo en la parte derecha de una declaración de variable estamos haciendo referencia al tipo, hacemos referencia a él por su nombre. Es decir, en cualquier parte del código, en la que pongamos el nombre para hacer referencia a un elemento previamente declarado, se considera una referencia. Hay veces que las referencias llevan asociadas además del nombre más información, por ejemplo, en la llamada a un subprograma se usan los argumentos. Ámbito Sabemos que una declaración asocia un nombre a un elemento del código. Una referencia básicamente es un nombre que indica que queremos usar el elemento al que se le ha asociado dicho nombre. Dos elementos no se pueden estar declarados con el mismo nombre si son del mismo tipo, ya que ante una referencia no se podría determinar a que elemento nos estamos refiriendo. Existen ocasiones en la que las referencias a elementos de distinto tipo se hacen exactamente igual, por lo tanto, dos de estos elementos no podrían estar declarados con el mismo nombre ya que no se podría determinar a qué elemento corresponden. Por otro lado, un programa puede ser muy grande y puede tener muchos elementos declarados. Además, podemos usar procedimientos programados por otras personas que declaren variables con unos nombres que nosotros ni siquiera sepamos que existen. Debido a que los nombres no pueden coincidir, necesitamos dividir los nombres en grupos para que se puedan repetir los nombres sin problemas y crear reglas para ver en que circunstancias se pueden dos elementos con el mismo nombre y en cuales no. 58 Sobre la información, su estructura y su-gestión Si en cualquier parte del código se pudiesen crear referencias a los elementos declarados en cualquier parte del código, al final la gestión de un código con esas características sería muy difícil. Por un lado para gestionar la colisión de nombres y por otro para restringir las referencias a los elementos declarados en cualquier parte del código se ha creado el concepto de ámbito. Un ámbito es un conjunto ordenado de declaraciones y referencias. Cada ámbito está asociado unidireccionalmente a otro ámbito llamado ámbito superior. Varios ámbitos pueden estar asociados al mismo ámbito formando un árbol. El ámbito raíz del árbol no tiene ningún ámbito superior. En un código fuente, cada una de las definiciones y referencias pertenecen a un ámbito determinado. Nos quedan por saber principalmente dos cosas. La primera es agrupar las declaraciones y referencias de un código fuente en distintos ámbitos. La segunda cosa es saber qué reglas existen en esta estructura jerárquica para resolver la colisión de nombres y qué elementos se pueden referenciar en cada uno de los ámbitos. Cuando se declara un subprograma existe un nuevo ámbito. Todas las declaraciones y referencias que existen en la definición de un programa pertenecen a ese nuevo ámbito. El ámbito superior de ese nuevo ámbito es al ámbito en el que se declara el subprograma. El nombre del subprograma pertenece al ámbito en el cual se declara el subprograma. El orden en el que aparecen las declaraciones y referencias dentro de un ámbito es el orden en el que aparecen en el código fuente. Las reglas que se aplican en los ámbitos son las siguientes: 59 Sobre la información, su estructura y su-gestión No pueden existir dos declaraciones en el mismo ámbito que asocien el mismo nombre a dos elementos del mismo tipo. Por ejemplo, en un mismo ámbito no puede haber dos variables que se llamen igual. Tampoco se pueden existir en un mismo ámbito dos declaraciones de elementos de distinto tipo si es posible es exista ambigüedad a la hora de determinar cual es el elemento referenciado. Por ejemplo, si existiese una función sin parámetros de nombre a y una variable de nombre a, cuando en un código fuente apareciese el nombre a en la parte derecha de una asignación no se podría determinar si es una referencia a la variable o si es una referencia a la función, por lo tanto existiría ambigüedad y esto no estaría permitido. Si pueden existir dos declaraciones en ámbitos distintos que asocien el mismo nombre a dos elementos del mismo tipo o en los que exista ambigüedad. Solo pueden existir referencias a declaraciones hechas en el mismo ámbito o en ámbitos superiores. Si la referencia aparece en el mismo ámbito que la declaración, la declaración debe aparecer en el código fuente antes de que aparezca la referencia. Es cierto que es posible hacer un tipo de declaraciones especiales en las que se asocia un nombre a una definición incompleta y posteriormente se asocia el mismo nombre a una definición que completa la anterior, pero esta característica no la vamos a considerar para no complicar mas el modelado. Tipos y Valores Estos dos elementos están íntimamente relacionados entre sí, así que los estudiaremos conjuntamente. Para nosotros un tipo representa un conjunto de valores. Para determinar si dos valores son del mismo tipo hay que comprobar si pertenecen al mismo conjunto. Los tipos predefinidos del lenguaje Pascal son el tipo de los enteros, el tipo de los caracteres, el tipo de los reales, el tipo de los lógicos, etc... Estos tipos predefinidos se encuentran declarados, es decir, se ha asociado a ellos un nombre para que puedan existir referencias a ellos, el 60 Sobre la información, su estructura y su-gestión nombre asociado al tipo de los enteros es “integer”, el de los caracteres es “char”, el de los reales es “real”, el de los lógicos es “boolean”, etc... Coerción de Tipos Los operadores y la sentencia de asignación tienen una característica que permite relajar la compatibilidad de tipos. En ciertas ocasiones, partiendo de valores de algunos tipos predefinidos se genera automáticamente un valor de otro tipo para satisfacer la compatibilidad de tipos. Por ejemplo, en la siguiente expresión: 5 / 3 Aparece el operador división real / que requiere operadores reales. Nosotros ponemos operadores de tipo entero y él, automáticamente genera unos valores de tipo real que representan la misma información que los de tipo entero. Nosotros no vamos a modelar esta capacidad de los valores o de los tipos. De tal forma que si un parámetro se define que es de tipo entero, no podrá ser de otro tipo para ser correcto. Diagramas de clases Vamos a hacer un breve repaso a cada una de las clases e interfaces usados para representar los elementos del lenguaje Pascal. Interfaz Evaluable Este interfaz es el que deben de implementar todas aquellas clases que vayan a representar elementos evaluables del código fuente. El único método que tiene este interfaz es el método que permite inspeccionar al objeto por el tipo del valor que devolverá en ejecución. Con el valor del tipo se harán las comprobaciones semánticas pertinentes. Los elementos evaluables son los valores, las referencias a variables, las referencias a constantes y las funciones. 61 Sobre la información, su estructura y su-gestión Quizás puede sorprender al lector que no hayamos mencionado las expresiones en ningún lugar de este documento, y parece que son de obligado nombramiento en esta última lista. La respuesta está en como vamos a modelar las expresiones, como simples funciones con unas características peculiares. Ya lo veremos cuando hablemos de las funciones predefinidas. Interfaz Sentencia Este interfaz es el que deben implementar todos aquellos elementos del lenguaje que representen a una sentencia. Le implementarán la sentencia de asignación, la sentencia condicional, la sentencia while, for, repeat y with, y la llamada a un procedimiento. De momento no tiene ningún método. Interfaz Definición Este interfaz es el que implementan todos aquellos elementos que representan una definición. Le implementarán la definición de tipo, de variable, de constante, de procedimiento, de función y de programa. De momento no tiene ningún método. Clase abstracta Declaración Una declaración es la asociación de un nombre a una definición, por tanto los atributos de esta clase son el nombre y la definición. Dependiendo del tipo de la declaración la definición será de una clase o de otra. Clase abstracta Referencia Esta clase representa todas las referencias que podamos encontrar en un código fuente. Tiene dos atributos, el nombre y la declaración en la que se da nombre al elemento al que referencia. Los métodos que tiene la clase son los de acceso al nombre y a la declaración. 62 Sobre la información, su estructura y su-gestión Clase Asignación Esta clase representa a la asignación. Tendrá un atributo para representar la referencia a la variable de la parte izquierda y un atributo evaluable de la parte derecha de la asignación. Clase Condicional Esta clase representa una sentencia condicional, el if. Tendrá tres atributos, uno para mantener la expresión, es decir, será del tipo Evaluable. El otro será la parte Then, que será una sentencia. Por último tendrá un atributo, para representar la parte Else si es que la tiene, si no, el valor de este atributo podrá ser null. Los métodos de la clase serán los necesarios para la consulta de sus atributos. Veamos la jerarquía de herencia de las sentencias: 63 Sobre la información, su estructura y su-gestión Sentencia Sentencia Compuesta Control Flujo Asignacion With Iterativa Condicional For Indeterminada While Repeat Clase abstracta DefTipo Hay varias formas de definir un tipo. Esta clase representa el concepto de definición de tipo. Sus subclases instancibles representan cada una de las formas concretas de definir un tipo. Clase DefTipoSinonimo Una forma de definir un nuevo tipo es mediante lo que se conoce como sinónimo. El nuevo tipo es exactamente igual que su sinónimo, pero al declararle hay que darle un nuevo nombre. La clase que represente esta definición de tipo tiene solamente un atributo de la clase RefTipo del que crea el sinónimo. 64 Sobre la información, su estructura y su-gestión Clase abstracta DefTipoConstruccion Esta clase, hermana de DefTipoSinonimo se usa para representar aquellas formas de crear nuevos tipos que no son iguales que otros, si no que su estructura es distinta a cualquiera que exista. Clase abstracta DefTipoConstSimple Esta clase representa a aquellos tipos que para su construcción no se basan en otros existentes. Sigue siendo abstracta porque hay mas subclasificaciones dentro de esta idea. Clase abstracta Ordinal Esta clase representa aquellos tipos considerados ordinales. No es instanciable porque no se puede definir un tipo ordinal directamente. Clase Enumerado Esta clase representa a los tipos enumerados. Es instanciable porque el usuario puede crear sus tipos enumerados. Cada nuevo tipo enumerado que aparezca en el código fuente corresponde con una nueva instancia de esta clase. Por tanto su atributo será una secuencia ordenada de identificadores. Clase Lógico Esta clase representa el tipo predefinido llamado boolean. Los identificadores de los que se compone este tipo son “true” y “false”, en este orden. Clase Entero Esta clase representa el tipo entero predefinido. Clase Caracter Esta clase representa el tipo carácter predefinido. 65 Sobre la información, su estructura y su-gestión Clase abstracta DefTipoConstCompuesto Esta clase representa aquellos tipos que se basan en otros tipos para su construcción. Es abstracta porque hay formas concretas de definir un tipo compuesto. Interfaz AccesoDefTipo Esta clase es un poco peculiar. Sabemos que hay dos formas de representar en el código un tipo. Podemos usar una referencia a un tipo existente (instancia de RefTipo) o bien podemos crear uno con su constructor, por ejemplo al crear un array. Hay partes del código en las que se puede indicar el tipo de cualquiera de las dos maneras. Por ejemplo al indicar el índice de un nuevo tipo array, podemos usar un tipo subrango existente, por ejemplo diasSemana, o bien podemos crear uno en ese mismo momento, por ejemplo 3..6. Resulta necesaria la construcción de un interfaz que implanten tanto RefTipo como DefTipo. Este será el interfaz AccesoTipo. El único método de este interfaz será el método para obtener el DefTipo asociado. Si es un RefTipo, este método devolverá el objeto de su atributo, si es DefTipo, este método devolverá a él mismo. Clase Array Esta clase representa a los tipos array. Un array de enteros será una instancia de esta clase. Tiene un atributo de la clase AccesoDefTipo. Clase Registro Representa un nuevo tipo registro. En el subconjunto de Pascal que estamos modelando no existen los registros variantes. Esta clase estará formada por una secuencia de DefVariable. Clase Puntero Representa al tipo puntero. Simplemente tiene un atributo de la clase RefTipo. 66 Sobre la información, su estructura y su-gestión Interfaz TipoDeValorAtomico Este interfaz lo van a implantar aquellos tipos cuyos valores son atómicos, indivisibles. Interfaz TipoDeValorCompuesto Este interfaz lo van a implantar aquellos tipos cuyos valores son compuestos. Veamos la jerarquía de clases de la definición de tipos: 67 Sobre la información, su estructura y su-gestión Acceso DefTipo RefTipo DefTipo DefTipoSinonimo DefTipoConstrucc Valor atomico Simple Ordinal Valor Estructurado Compuesto Real Subrango Registro Puntero Entero Char Array Enumerado Logico Clase DefConstante Esta clase representará la definición de una constante. No consideramos el nombre como parte de la definición de la constante, si no que es en la declaración de la constante donde se asocia al nombre a la definición. Por tanto, una definición de constante solo tiene un atributo de tipo valor. 68 Sobre la información, su estructura y su-gestión Clase DefVariable Esta clase representará la definición de una variable. Seguimos con la idea de que el nombre se asocia a la variable en la declaración, y no en la definición. Por tanto la información que tiene que albergar la clase DefVariable es el tipo de la variable. Por tanto tiene un atributo de la clase AccesoTipo. Clase DefSubprograma Esta clase representará la definición de un subprograma. Estará formada por un cuerpo, y las secuencias de declaraciones, en las que estarán las declaraciones de parámetros, de tipos, de subprogramas, etc... Clase DefFunción Esta clase va representar a las funciones. El único atributo que tiene, además de los heredados es la referencia al tipo, es decir, tiene un atributo de la clase RefTipo. Como hemos comentado mas arriba, en nuestro modelado de Pascal no existen las expresiones. A nuestro parecer todo los aspectos relacionados con expresiones, que hacen que sean diferentes a las funciones predefinidas son aspectos sintácticos. Por este motivo vamos a modelar las expresiones de operadores predefinidos como llamadas a funciones predefinidas. Será en el momento de la conversión de texto a objetos cuando se tengan que tomar las decisiones referentes a asociatividad y precedencia, pero estas cuestiones no se va a reflejar en el modelo. Clase DefParametro Esta clase que representa a los parámetros, amplía la clase DefVariable para poder especificar si los parámetros son por referencia o por valor. Veamos la jerarquía de definiciones: 69 Sobre la información, su estructura y su-gestión Definicion DefVariable DefParametro DefConst DefSubprograma DefFuncion DefTipo DefProcedure DefPrograma Para las declaraciones, tenemos la siguiente jerarquía: 70 Sobre la información, su estructura y su-gestión Declaracion DecVariable DecConst DecParametro DecSubprograma DecFuncion DecTipo DecProcedure DecPrograma Clase abstracta RefSubprograma Esta clase va a representar la llamada a un subprograma, ya sea una función o un procedimiento. Es abstracta porque será necesario subclasificarla en RefFuncion y RefProcedimiento. Obviamente hereda de la clase Referencia. Esta clase tendrá el atributo nombre y declaración, que le vienen por herencia de la clase Referencia y la lista de parámetros actuales. Los métodos de la clase, como es habitual, serán para consultar sus atributos. Clase RefProcedimiento Esta clase representa una llamada a un procedimiento. Hereda de la clase RefSubprograma. No incorpora ningún atributo ni método más. El cambio con respecto a la clase padre es que es instanciable y que implementa el interfaz sentencia. 71 Sobre la información, su estructura y su-gestión Clase RefFuncion Esta clase representa una llamada a una función. Hereda de la clase RefSubprograma. El cambio respecto a la clase padre es que es instanciable y que implementa el interfaz evaluable. El implementar el interfaz Evaluable implica que tiene que implementar el método para determinar el tipo de la función, cosa que hará preguntándoselo a la definición asociada. Hay que recordar que las expresiones se van a modelar como llamadas a funciones, por tanto, instancias de la clase RefFunción también representarán expresiones con operadores. La jerarquía de clases será: Referencia RefVariable RefConst RefSubprograma RefTipo Evaluable RefParametro RefFuncion 72 RefProcedure Sentencia Sobre la información, su estructura y su-gestión No tenemos que olvidar que un valor es evaluable, por tanto: Evaluable Valor Veamos ahora de una forma muy esquemática, las relaciones de asociación y composición hacia cada uno de los cuatro conceptos generales: Asignacion ControlFlujo ParActuales * Evaluable Declaracion DefSubprograma * * Definicion Declaracion 73 Sobre la información, su estructura y su-gestión ControlFlujo ControlFlujo DefSubprograma SentenciaCompuesta * Sentencia Clase Ámbito Como hemos visto, los ámbitos están ligados a la definición de los subprogramas, por tanto, la gestión del ámbito podría hacerse en la propia definición del subprograma, no obstante, debido a que las tareas del ámbito son un poco mas complejas que el resto de funciones de los elementos del modelo, hemos decidido crear un delegado para esta tarea. La clase ámbito es la elegida para atender todas las peticiones que deberían llegar a la definición del subprograma. Instanciación de objetos. Análisis Léxico-Sintáctico. Hemos visto de qué forma vamos a modelar un subconjunto del lenguaje Pascal. El objetivo es que cada elemento de un código fuente del Pascal se represente con una instancia. Ahora nos falta obtener esa representación en memoria a partir de un código escrito de forma textual. En este tema veremos cómo hemos construido el analizador léxico sintáctico y cuales son las rutinas semánticas asociadas a cada producción para instanciar los objetos. Para hacer el análisis léxico-sintáctico hemos usado una herramienta de generación automática. La herramienta se llama JavaCC. Es una herramienta al 74 Sobre la información, su estructura y su-gestión estilo de los tradicionales lex y yacc pero que genera código en lenguaje Java. No vamos a detallar el funcionamiento de esta herramienta ya que queda fuera del ámbito de este TFC. De todos modos, con el objetivo de que el lector no se pierda, daremos un breve repaso a sus características más importantes. La especificación léxica y sintáctica es conjunta, se realiza en el mismo fichero. A partir esta definición y las rutinas semánticas asociadas a las producciones la herramienta genera una clase en Java. Esta clase se incorpora en nuestro programa como cualquier otra clase. Al instanciarse se la pasa un flujo de entrada de texto y cuando queramos analizar un código fuente proveniente de ese flujo y ejecutar las rutinas semánticas asociadas simplemente ejecutamos un método. El método de análisis es LL(N), esto quiere decir que ajusta automáticamente el número de caracteres por adelantado necesarios para tomar decisiones sobre que producción expandir. Se implementa usando el método predictivo-recursivo. Respecto a las rutinas semánticas, la herramienta permite asociar líneas de código a las producciones para que estas se ejecuten justo después de haber aplicado una producción. También es posible el paso de parámetros a las producciones y que las producciones devuelvan valores. La gramática usada para definir Pascal, escrita en formato EBNF es la siguiente: programa ::= <PROGRAM> id ";" bloque "." bloque ::= declaracion grupoSent declaracion ::= ( decCte )? ( decTip )? ( decVar decSubprograma )? decSubprograma ::= ( ( decProcedimiento | decFuncion ) )+ decCte ::= <CONST> ( unaConst ";" )+ 75 )? ( Sobre la información, su estructura y su-gestión unaConst ::= <ID> "=" valorSimple decTip ::= <TYPE> ( unTipo ";" )+ unTipo ::= <ID> "=" tipoEstr tipoEstr ::= ( estrTab | estrReg ) estrTab ::= <ARRAY> "[" valorSimple ".." valorSimple "]" <OF> <ID> estrReg ::= <RECORD> listaVar ( ";" listaVar )* <END> decVar ::= <VAR> ( listaVar ";" )+ decProcedimiento ::= <PROCEDURE> id ( "(" parFormales ")" )? ";" bloque ";" decFuncion ::= <FUNCTION> id ( "(" parFormales ")" )? ":" id ";" bloque ";" parFormales ::= listaParam ( ";" listaParam )* listaParam ::= ( <VAR> )? id ( "," id )* ":" id listaVar ::= id ( "," id )* ":" id grupoSent ::= <BEGIN> sentencia ( ";" sentencia )* <END> sentencia ::= ( ( asignacion | condicional | ciclica | llamada | grupoSent ) )? asignacion ::= id ":=" expresion condicional ::= <IF> expresion <THEN> sentencia ( <ELSE> sentencia )? ciclica ::= <WHILE> expresion <DO> sentencia llamada ::= id ( "(" parLlamada ")" )? parLlamada ::= ( expresion ) ( "," expresion )* expresion ::= exprSimple ( opRel exprSimple )? exprSimple ::= ( signo )? termino ( opAdt termino )* termino ::= factor ( opMul factor )* 76 Sobre la información, su estructura y su-gestión factor ::= ( valorSimple | nombreEval | <NOT> factor | "(" expresion ")" ) nombreEval ::= id selector ::= "[" expresion "]" | "." <ID> valorSimple ::= ( signo )? <NUMERO> opRel ::= ( "<" | ">" | "<=" | ">=" | "=" | "<>" ) opAdt ::= ( signo | "OR" ) opMul ::= ( "*" | "DIV" | "MOD" | "AND" ) signo ::= ( "+" | "-" ) id ::= <ID> Partiendo de esta gramática y de las clases del modelo, tenemos que definir unas pautas para ir creando instancias a medida que vamos haciendo el análisis léxico-sintáctico. El momento del análisis en el que se crean las instancias va a estar muy determinado por los parámetros que tengan los constructores de las clases. Veamos esto con un ejemplo. La parte de la gramática que especifica la estructura textual de una asignación escrita con la sintaxis de JavaCC y sin rutinas semánticas es la siguiente: void asignacion() : { id() ":=" expresion() } Podemos poner una rutina semántica en cualquier espacio entre terminales y no terminales, y será ejecutada cuando el analizador léxico-sintáctico pase por ese lugar. La pregunta es ¿cuando instanciar el objeto de la clase asignación?. Si el constructor de la clase asignación, requiere la parteIzquierda y la parte 77 Sobre la información, su estructura y su-gestión derecha de la asignación, no podemos instanciar justo después de que se reconozca el terminal “:=”, porque en ese momento no sabemos cuál es la parte derecha de la programa. En nuestras manos también está determinar qué parámetros se requieren para instanciar un objeto de la clase asignación, entonces... ¿qué hacemos?. La idea general que vamos a seguir, es que cualquier instancia de alguna clase que modela el lenguaje Pascal, tiene que ser estructuralmente correcta. Vamos a explicar que queremos decir con esto. Por ejemplo, si una asignación siempre tiene una referencia a una variable en su parte izquierda y un elemento evaluable en su parte derecha, no vamos a permitir que se instancie ningún objeto de la clase Asignacion que no tenga un objeto RefVariable en su atributo parteIzquierda y un objeto que implente el interfaz Evaluable en su atributo parteDerecha. Dicho de otro modo, en el constructor de la clase Asignacion hay que pasarle como parámetros la parteIzquierda y la parteDerecha. Siguiendo esta idea, la especificación léxico-sintáctica junto con las rutinas semánticas de una asignación será: Asignacion asignacion() : { Evaluable evaluable; Nombre n; } { n = id() ":=" evaluable = expresion() { return new Asignacion(new RefVariable(n),evaluable); } } La rutina semántica se ejecuta cuando se ha completado el reconocimiento de la asignación. De este modo sabemos cual es su parte izquierda y cuál es su parte derecha. Vamos a repasar lo que hemos hecho. Cada parte de una asignación genera un objeto que la representa. Estos objetos son recogidos en variables temporales. Partiendo de los objetos obtenidos en la aplicación de los 78 Sobre la información, su estructura y su-gestión no terminales construimos el objeto que representa la producción actual y le devolvemos a una producción superior. En la llamada al constructor de la clase Asignacion vemos como creamos un objeto de la clase RefVariable. En algunos casos, la información que obtenemos de los no terminales al ser aplicados no se le pasa directamente al constructor, si no que sirve para construir un objeto que será el que se le pase al constructor. Por último podemos observar la clase Nombre. Esta clase, con un String como único atributo representa un nombre o identificador en el código fuente. Veamos otro ejemplo en el que aplicamos esta idea para instanciar los objetos. El caso de la sentencia condicional. Hemos dicho que el requisito para instanciar un objeto es que éste sea estructuralmente correcto. Una sentencia condicional se puede considerar estructuralmente correcta si tiene la expresión y la parte then. La parte else es opcional. Por tanto, para ser coherentes, los parámetros del constructor son la expresión y la parte then, y dejamos la posibilidad de incorporar posteriormente la parte else. La especificación léxicosintáctica de una sentencia condicional sin rutinas semánticas sería: void condicional(): { <IF> expresion() <THEN> sentencia() [<ELSE> sentencia()] } Los corchetes indican que esa parte de la producción puede que aparezca o no. Al incorporarle las rutinas semánticas, la especificación quedaría: Condicional condicional() : { Condicional condicional; Evaluable evaluable; Ejecutable ejecutable; } { <IF> evaluable = expresion() <THEN> ejecutable = sentencia() 79 Sobre la información, su estructura y su-gestión { condicional = new Condicional(evaluable,ejecutable); } [ <ELSE> ejecutable = sentencia() { condicional.setParteElse(ejecutable); } ] { return condicional; } } Podemos observar que mantenemos la misma idea de que el elemento sea estructuralmente correcto y sólo añadimos la parte opcional cuando esta existe. Otra situación que tenemos que considerar a la hora de ir creando las instancias a partir del código fuente es cuando tenemos secuencias de elementos. Como hemos visto al modelar Pascal, en muchas clases aparecen atributos que son secuencias de objetos. Una secuencia estará representada mediante una clase. Entonces nos planteamos la siguiente pregunta, ¿es lo mismo una atributo secuencia con una instancia que no tiene elementos y un atributo secuencia con el valor null? Para nosotros no. Por ello vamos a considerar que una instancia de una clase que tiene atributos secuencia es estructuralmente correcta cuando tiene el atributo secuencia con una instancia, aunque dicha instancia no tenga ningún elemento. Por esto, cuando instanciamos un objeto que tenga algún atributo secuencia, o bien le indicamos todos los elementos de la secuencia en el constructor o bien el constructor de la clase instancia un objeto instancia vacío. Vamos a usar la segunda opción, en el constructor se va a instanciar la secuencia sin elementos. Se necesita que cada clase con atributos secuencia tenga un método de acceso a dichas secuencias para que podamos insertar los elementos en la secuencia. Para comprobar como usamos esta idea en la instanciación veamos un ejemplo. La secuencia de constantes de un programa o subprograma. void programa() : { <PROGRAM> id() ";" bloque() "." } void bloque() : 80 Sobre la información, su estructura y su-gestión { [ [ [ [ decCte() decTip() decVar() decSubprograma() grupoSent() ] ] ] ] } void decCte() : { <CONST> ( unaConst() ";" )+ } void unaConst() : { id() "=" valor() } Lo que haremos ahora será añadir las rutinas semánticas referentes a las constantes para no complicar demasiado la especificación. La especificación quedaría: DefPrograma programa() : { DecPrograma decPrograma; DefPrograma defPrograma; Nombre n; } { <PROGRAM> n = id() ";" { defPrograma = new DefPRograma(); decPrograma = new DecPrograma(n,defPrograma); } bloque(defPrograma) "." { return decPrograma; } } void bloque(Defprograma defPrograma) : { [ decCte(defPrograma.getConstantes()) [ decTip( ... ) ] [ decVar( ... ) ] [ decSubprograma( ... ) ] grupoSent( ... ) } void decCte(SecuenciaDefConstante constantes) : { <CONST> ( unaConst(constantes) ";" )+ } void unaConst(SecuenciaDefConstante constantes) : { 81 ] Sobre la información, su estructura y su-gestión Nombre n; Valor v; } { n = id() "=" v = valor() {constantes.addElement(new DecConstante(n,new DefConstante(v))); } } Ambigüedad sintáctica Hasta ahora, con los ejemplos que hemos visto, parece que simpre podemos determinar sintácticamente de que clases hay que instanciar los objetos. Pero supongamos que en el proceso de análisis léxico-sintáctico nos encontramos con la siguiente línea: variable := identificador; Si esta línea es correcta dentro de su programa, la parte izquierda de la asignación es una referencia a una variable, es decir, habrá que instanciar un objeto de la clase RefVariable. Pero en la parte derecha no sabemos a que corresponde ese identificador. Podría ser una referencia a una variable, una referencia a una constante o una referencia a una función sin parámetros. No sabríamos si instanciar un objeto de la clase RefVariable, RefConstante o RefFuncion. Ante este problema tenemos dos elecciones. Podemos instanciar un objeto en el que no se tenga que especificar en este momento que tipo de referencia es, por ejemplo instanciar un objeto de una clase llamada RefIncognita. O bien podemos hacer que cuando nos encontremos en esta situación, se tenga acceso al ámbito en el que se encuentra la asignación y así pueda determinar a que corresponde la referencia. Hemos considerado mejor idea la segunda. Así que en cada producción en la que exista la posibilidad de que dentro aparezca alguna ambigüedad de este tipo se ha de pasar como parámetro el gestor del ámbito en el que se encuentre la ambigüedad. 82 Sobre la información, su estructura y su-gestión Cuando el análisis encuentre una ambigüedad se le pedirá al gestor del ámbito que determine a que elemento corresponde el nombre indicado. Cuando lo haya determinado nos devolverá una instancia de la clase correspondiente dependiendo de donde encuentre el identificador. Si no encuentra el identificador declarado, hay un error semántico. Patrón Capa Observadora Llegados a este punto hemos diseñado una jerarquía de clases que representa los conceptos que aparecen en el lenguaje Pascal. También hemos dado unas pautas para usar un analizador léxico-sintáctico para crear la jerarquía de objetos partiendo de un código fuente escrito en texto. Nos falta una cosa fundamental en nuestro diseño, ¿dónde hacemos las comprobaciones semánticas?. En el análisis léxico-sintáctico hacemos una tarea semántica para evitar cierta ambigüedad. Pero nos quedan las tareas semánticas del resto de los elementos del código. Tenemos que enlazar las referencias a sus definiciones. Si no existe una definición con ese nombre, o el tipo de definición no corresponde con el tipo de la referencia existe un error semántico. Si existe la definición tenemos que asociar la referencia a la definición. En el caso de las referencias a funciones o programas, además de comprobar que existe un parámetro con el mismo nombre, hay que determinar si los parámetros son compatibles en cuanto número y tipo. En las asignaciones también hay que hacer las comprobaciones de tipos pertinentes. Otra característica deseable sería poder visualizar el código fuente en pantalla. Sería buena idea poder tener dos formas de visualizar el código fuente. Una de ellas podría ser en forma textual. Sería bastante útil que esta forma textual tuviese la misma sintaxis original de Pascal. Otra de ellas podría ser gráfica. Debido a la naturaleza eminentemente jerárquica del código, 83 Sobre la información, su estructura y su-gestión podríamos usar una visualización en forma de árbol del código, con la posibilidad de expandir o colapsar ciertos nodos del árbol y así poder ver unas partes u otras del código. Vamos a analizar desde un punto de visto un poco mas abstracto que es lo que queremos ahora. Tenemos una estructura de objetos en memoria. Queremos que la información que representan esos objetos se vea representada de varias formas. También queremos que dependiendo de la información que representan algunos de esos objetos se cambien modifiquen para crear los enlaces entre las referencias y las definiciones. Para hacer las comprobaciones de compatibilidad de tipos y el resto de comprobaciones semánticas también tenemos que comprobando ciertas cosas sobre la información que representan. Vemos que la parte referente a comprobación de tipos y enlace de la referencia con el elemento referenciado son partes esenciales para que la jerarquía de objetos represente un programa correcto. En cambio, la visualización del programa es una característica deseable pero no esencial para que la estructura de objetos represente un programa correcto. Otro detalle importante es que el número de visualizaciones diferentes que se pueden obtener del mismo programa puede crecer bastante. Podría ser interesante obtener un tercer tipo de visualización, híbrida entre el modelo textual y el arbóreo. Las conclusiones que podemos sacar es que necesitamos que en nuestra estructura de objetos se comprueben ciertas cosas y se cambien otras. También necesitamos poder obtener la información que representa la estructura de objetos y visualizarla. Hemos decidido que aquellas acciones sobre la estructura de objetos que permiten obtener un programa correcto han de implementarse como métodos dentro de las clases del lenguaje Pascal. Y las acciones sobre la estructura de 84 Sobre la información, su estructura y su-gestión objetos que podríamos considerar auxiliares, como la visualización, las implementaremos en otras clases. La forma de implantar las rutinas semánticas en las clases y las acciones que crean los enlaces va a consistir simplemente en añadir un método en las clases mas altas de la jerarquía de herencia llamado resuelveReferencias. La forma de implementar las clases para la visualización es algo mas compleja, vamos a verlo con un poco mas de detalle. Queremos crear otras clases que accedan a los atributos de las clases que ya tenemos. Veamos distintas formas de crear estas clases: Una clase y una instancia. Una instancia de esa clase va recorriendo el árbol. Se asocia a cada uno de los objetos del árbol y obtiene su información para visualizarla. Este enfoque presenta bastantes desventajas, por un lado, ¿Cómo sabe un objeto de esta clase lo que le puede preguntar al objeto asociado? ¿Le pregunta el tipo y entonces determina que mensajes lanzarle? Obviamente este no es un enfoque orientado a objetos. Queda desechado. Una clase y varias instancias. Tenemos el mismo problema que en el caso anterior, aunque exista una instancia por cada instancia de la estructura de objetos, no sabremos cuál es su tipo. Varias clases y una instancia (por clase). Si existe una clase de acceso a la información por cada clase del modelo de Pascal, tendremos una doble jerarquía de clases. Esta solución permite que un objeto de acceso a la información sólo se asocie a objetos de la clase que conoce, a objetos hermanos. Por tanto, en la implantación de los métodos de cada una de estas clases el tratamiento de la información es dependiente de la clase hermana. Una clase por instancia parece un idea razonable siempre que no se necesite un acceso a la información de forma aleatoria. Si se requiere un acceso secuencial a la 85 Sobre la información, su estructura y su-gestión información, es viable que los objetos recorran el árbol y cada uno se encargue de las instancias de su clase hermana. No obstante, existen formas de implementar el acceso aleatorio con una única instancia por clase, pero son un poco más complejos de implementar. Varias clases y varias instancias. Con una jerarquía de clases paralela y una jerarquía de objetos paralelos se permite un cómodo acceso a la información de forma aleatoria. Lo que ocurre es que existe un mayor consumo de memoria. Esta opción es la más cómoda de implementar y la que mas ventajas ofrece. Es la opción que usaremos. Hemos hablado un poco en general de cómo se podrían añadir funcionalidades de acceso a la información de una estructura de objetos sin modificar las clases de esa estructura de objetos. Vamos a ver un ejemplo concreto. Queremos representar en forma de árbol un código fuente. Para ello vamos a usar la clase JTree de Java. Esta clase requiere, entre otras cosas, que los nodos del árbol tengan unos métodos especiales de acceso a sus elementos hijos. Nuestra estructura de objeto no implementa esos métodos de acceso. No estructura de objetos nunca va a implementar esos métodos de visualización. Entonces necesitamos establecer un puente entre la funcionalidad que se requiere de los nodos de un árbol para visualizarse y la información que puede proporcionar nuestro árbol de objetos del programa. Dicho de otra forma, necesitamos obtener información de cada uno de los elementos de la estructura de objetos y con esa información haremos frente a las preguntas realizadas por el JTree. La solución que hemos comentado, es que exista una clase por cada clase del modelo de Pascal. Por claridad, en nuestro prototipo hemos llamado a cada una de estas clases hermanas con el nombre de la clase del modelo seguida de TreeNode, por ejemplo la clase Asignación tiene como hermana la clase AsignaciónTreeNode. Las clases TreeNode tienen métodos de consulta que son lanzados por la 86 Sobre la información, su estructura y su-gestión instancia de la clase JTree en la que se visualice el árbol. Para responder responder a esos mensajes, la instancia de la clase TreeNode pregunta a su objeto asociado con los métodos de acceso. Sabe cómo preguntarle porque conoce sus métodos, al fin y al cabo, es su hermano. Hemos visto un ejemplo de cómo una capa observadora se usa para visualizar código. Podríamos construir otra capa observadora para hacer cualquier otro tratamiento de la información del código. En el último tema veremos varias ideas que se pueden implantar usando este patrón. 3.Tiempo de Ejecución Introducción En el tiempo de compilación hemos obtenido una representación orientada a objetos de un código fuente. Este modelado nos permite aclarar muchos de los conceptos que se manejan a la hora de crear un programa, de escribir el código fuente. En el tiempo de ejecución queremos hacer lo mismo. Queremos modelar los conceptos que aparecen en la ejecución usando la tecnología orientada a objetos. Sabemos que un proceso es un programa en ejecución. El programa actúa como definición del proceso. Un proceso concreto, es una particularización de un programa. Un programa se puede ejecutar muchas veces con resultados diferentes. Un programa define un conjunto de posibles procesos. En la tecnología orientada a objetos, las definiciones se representan con clases. Y las particularizaciones, se representan con objetos. 87 Sobre la información, su estructura y su-gestión Modelado de la ejecución de un programa escrito en Pascal Introducción Ahora nuestra definición es un programa y su particularizaciones son procesos. Por tanto, un programa cualquiera le tendremos que representar por un conjunto de clases y su ejecución por un conjunto de objetos. Existen muchas formas de plasmar esta idea, la de que un programa concreto esté representado por un conjunto de clases relacionadas y cada una de sus ejecuciones estén representadas por un conjunto de objetos. Modelar el tiempo de compilación o desarrollo ha sido más sencillo, básicamente teníamos que representar orientado a objetos la estructura que podía tener cualquier código fuente de Pascal. La gramática léxico-sintáctica nos ha servido de inicio en el proceso, aunque pronto nos hemos ido alejando más y más de ella. En ejecución estamos perdidos, los conceptos no se plasman en ningún sitio de manera algo formal, al menos un código fuente está en modo texto. En el modelado del lenguaje no hay profundas diferencias entre la clase que representa un valor y la clase que representa la referencia a una variable, al fin y al cabo, son clases que representan información y tienen una pequeña lógica asociada para ver si esa información es correcta. En el modelado del tiempo de ejecución la diferencia entre la definición de una variable y la definición de un programa es abismal, la diferencia entre la llamada a una función y la definición de un tipo son cosas totalmente distintas. Mostraremos la idea que proponemos de modelado del tiempo de ejecución. Sentencias Cada una de las sentencias de un programa se va a representar por una clase. Cuando exista en memoria una instancia de una de esas clases, conceptualmente consideraremos que la sentencia a la que representa se está ejecutando. En el momento en que esa instancia deja de existir consideraremos 88 Sobre la información, su estructura y su-gestión que se ha finalizado la ejecución de la sentencia a la que representa. Todas las clases que representen una sentencia heredarán de una clase común. Elementos Evaluables Cada uno de los elementos evaluables que existan en un programa se va a representar por una clase. Cuando exista en memoria una instancia de alguna de estas clases, conceptualmente consideraremos que el elemento evaluable al que representa está evaluándose. Cuando esa instancia deja de existir consideraremos que se ha finalizado la evaluación. Todas las clases que representen un elemento evaluable heredarán de una clase común. Tiene que existir alguna forma de obtener un valor del elemento evaluable justo antes de que desaparezca de memoria. Variables, constantes y parámetros Cualquier variable, constante o parámetro que aparezca en el código se va a representar por una clase. Cuando exista en memoria una instancia de alguna de estas clases, conceptualmente vamos a considerar que existe un dato, un ítem de información. Cuando esa instancia deja de existir consideramos que ya no existe ese ítem de información. Todas las clases que representan una variable, constante o parámetro, heredarán de una clase común. Valores Los valores NO se representan por clases distintas. Todos los valores son instancias de la clase Valor. Lo que ocurre es que un valor puede aparecer en el código fuente en algún lugar evaluable. Por tanto deberá comportarse como un elemento evaluable en esos casos. 89 Sobre la información, su estructura y su-gestión Tipos En Pascal los tipos no se representan de ninguna forma en tiempo de ejecución. Cuando se está ejecutando un programa en Pascal en ningún momento es necesario saber de que tipo es un valor o que tipo devuelve una función. No es necesario porque en tiempo de compilaciones ya se han dejado las cosas “preparadas” para que el tipo de todos los valores sea el esperado. Información y Memoria Como es lógico, en el tiempo de ejecución no se había hablado de la memoria del ordenador. Conocemos la memoria como el lugar donde están las variables. Queremos hacer una representación conceptual del concepto de memoria, así que la vamos a renombrar. En realidad, el conjunto de variables, constantes y parámetros, es decir, los ítems de información que se manejan en un programa representan la información que es capaz de manejar ese programa. Basándonos en esta idea, en el modelo de tiempo de ejecución existirá una clase llamada Información que esté formada por todos los ítems de información existentes en el programa en un instante determinado. No vamos a hacer distinción entre memoria estática y dinámica, desde un punto de vista conceptual todo es información. La diferencia entre los items de información de memoria estática es que el momento de creación y el momento de destrucción se pueden saben en tiempo de ejecución y en la memoria dinámica el momento en que se crean y destruyen los ítems de información es controlable por el propio programa. Referencias a Variables, Constantes o Parámetros Hay veces que la referencia a una variable, constante o parámetro es considerada como un elemento evaluable, por tanto se le aplica lo dicho para los elementos evaluables. Hay ocasiones en que una referencia a una variable o 90 Sobre la información, su estructura y su-gestión parámetro se hace en la parte izquierda de una asignación. En tiempo de ejecución, la función de una referencia es el acceso al ítem de información de la variable a la que referencia. Cada referencia a una variable, constante o parámetro se va a representar con una clase. Si la referencia es tratada como un elemento evaluable, se aplica lo visto para elementos evaluables. Es decir, cuando exista en memoria una instancia de alguna de estas clases, conceptualmente consideraremos que el elemento evaluable al que representa está evaluándose. Si la referencia a una variable o parámetro, se encuentra en un lugar donde se necesite acceder al item de información al que está referenciando, la instancia de la referencia se trata de distinta forma. En este caso, cuando existe en memoria una instancia de una referencia a una variable significa conceptualmente que se están realizando las acciones necesarias para acceder al ítem de información de esa variable. Ámbito Ya sabemos que significa el concepto de ámbito en tiempo de compilación. Un ámbito es un conjunto de declaraciones y referencias. Una vez que no existe ninguna colisión entre las declaraciones, las referencias se enlazan a los elementos declarados. Sólo los elementos declarados en el ámbito actual o ámbitos superiores son accesibles desde el ámbito actual, es decir pueden ser referenciados. Como sabemos existen elementos de distintos tipos que son declarados y referenciados. Los subprogramas, las constantes, las variables, los parámetros y los tipos. En todos ellos existe el concepto de ámbito en tiempo de compilación. Pero veamos si el concepto de ámbito sigue apareciendo en tiempo de ejecución. Hemos dicho que los tipos no se representan en tiempo de ejecución, por tanto el ámbito en los tipos no se va a reflejar tampoco. El ámbito en los subprogramas sirve para saber qué subprogramas se pueden referenciar en una determinada zona de código. En tiempo de compilación se determina que las 91 Sobre la información, su estructura y su-gestión referencias a los subprogramas son correctas, es decir, referencian subprogramas declarados en su ámbito o superiores. Por tanto, todas las referencias a programas que aparezcan en tiempo de ejecución serán correctas. Cuando en el proceso de ejecución se tenga que ejecutar la llamada a un subprograma, se ejecutarán unas acciones previas y al final se ejecutará el subprograma referenciado. Esto indica que el ámbito referente a subprogramas en ejecución no se va a considerar. Según hemos comentado antes, si una instancia de un elemento ejecutable se encuentra en memoria, se considera que ese elemento se está ejecutando. Supongamos que el elemento ejecutable al que representa es la siguiente asignación: a := 4; La instancia se crea en el momento de ejecutar esa asignación. El tiempo que una instancia de esa asignación esté en memoria, depende de los pasos que haya que seguir para asociar el valor 4 al ítem de información que representa a la variable de nombre a. No se preocupe si se pierde en los pasos que hay que seguir mas adelante lo explicaremos con mucho mas detalle. Lo que queremos mostrar aquí es que el tiempo que pertenece en memoria la instancia que representa a la asignación, depende solamente de la asignación o de alguna de sus partes, pero no depende de nada más. También habíamos comentado antes que una instancia de un elemento evaluable representa que en ese momento se está produciendo la evaluación del elemento. La instancia se crea en el momento en que se requiera evaluar el elemento. De nuevo vemos que el tiempo que está la instancia en memoria depende del elemento evaluable o de los elementos de los que se componga, pero no depende del lugar donde se encuentre la evaluación. Dicho de otra 92 Sobre la información, su estructura y su-gestión manera, independientemente del lugar donde se encuentra un elemento evaluable el tiempo que tarda en evaluarse es el mismo. Pero veamos que ocurre con las variables. Hemos dicho que cada variable de un programa se representa con una clase. Si existe una instancia de una clase que representa a una variable concreta significa que existe un dato o ítem de información. Si esa instancia ya no existe, consideramos que ya no existe esa información. La pregunta ahora es ¿cuanto tiempo está en memoria un instancia de una variable? ¿cuánto tiempo permanece un ítem de información concreto en el programa? ¿cuándo se instancia una variable? ¿cuándo se destruye? La respuesta a estas preguntas tiene mucho que ver con el ámbito. Antes de ver la relación repasemos un poco en que momento se instancian otros objetos del tiempo de ejecución. La ejecución de un procedimiento se realiza cuando aparece una referencia a él en el cuerpo de otro subprograma, de su propio cuerpo, o del cuerpo del programa principal. Es decir, la instancia que represente la ejecución de un subprograma se creará a raíz de la creación de la instancia que representa la ejecución de la llamada al subprograma. Como vemos, la instancia que representa la ejecución de un subprograma se crea cuando se hace referencia a ese subprograma. Con las variables no ocurre eso. La referencia a una variable no invoca la creación de un instancia de esa variable. Dependiendo del lugar del código donde aparezca esa referencia implicará unas cosas u otras. Por ejemplo si aparece en una expresión será considerada como evaluable, y si aparece en la parte izquierda de una asignación será usada para acceder al item de información al que esté asociado. Por lo tanto, siguen sin respuesta las preguntas ¿cuándo se instancia una variable? ¿cuándo se destruye? ¿cuanto tiempo está en memoria un instancia de 93 Sobre la información, su estructura y su-gestión una variable? ¿cuánto tiempo permanece un ítem de información concreto en el programa?. Veamos, hemos dicho que la variable se tiene que instanciar antes de que se produzca ninguna referencia a ella en el programa. Es decir, el item de información tiene que existir antes de que a través de una referencia sea usado. También parece lógico que el ítem de información de una variable se destruya si ya nadie puede acceder a él. Entonces el momento en el que se instancia una variable y el momento en el que se destruye dependen del lugar donde se pueda hacer referencia a la variable. Antes de que comience ese lugar hay que instanciar las variables y justo cuando ese lugar acabe, habrá que destruir las variables. Como el lector ya sabrá, es el ámbito el que determina precisamente eso. Por tanto, la representación del ámbito referente a la declaración y referencia a las variables se verá reflejado en ejecución en que va a determinar el momento en el que se creen las instancias de las variables y el momento en el que se destruyan dichas instancias. Diagrama de clases La idea de que una instancia de un objeto representa que en ese momento se está produciendo la ejecución, o evaluación, o que existe un ítem de información o que se está accediendo a uno parece bastante razonable. Pero existen algunas restricciones al llevar esta idea a código Java. Por ejemplo la destrucción explícita de objetos no es posible. Otro pequeño inconveniente es que en los constructores no se pueden devolver objetos. Ante esta situación hemos optado por modificar un poco la idea inicial. La ejecución, evaluación, etc… se van a representar con una instancia, pero además esta instancia tiene que estar activa. Vamos a considerar que una instancia comienza a estar activa cuando se le lance un determinado método. De igual forma, vamos a representar la destrucción como “desactivar una instancia”, desactivaremos una instancia cuando le lancemos un determinado método. Aunque hayamos 94 Sobre la información, su estructura y su-gestión tomado esta decisión por problemas de implementación, vamos a considerar que un objeto no existe para nosotros si no está activo y deja de existir cuando se desactiva. Dicho de otro modo, justo después de instanciar un objeto hay que activarle y justo después de que el objeto haya acabado su tarea hay que desactivarle. Hemos dado una idea muy general de cómo vamos a representar los conceptos que aparecen en Pascal en tiempo de ejecución. Intentaremos progresivamente entrar cada vez en más detalles. Basándonos en el modelado del tiempo de compilación iremos dando un poco más detallado el modelado en tiempo de ejecución. Repasemos, la existencia de un objeto activo en memoria representa ejecución, evaluación, ítem de información o acceso a un ítem. Cuando se desactive una instancia supone que se ha acabado la ejecución, la evaluación, que el ítem de información ya no existe o que ya se ha accedido al ítem. Sentencia Partiendo de cada una de las instancias de aquellas clases del tiempo de compilación que implementen el interfaz Sentencia se ha de crear una clase que herede de la clase EjecucionSentencia. Las instancias de la clase EjecucionSentencia van a representar la ejecución de la sentencia. Se considera que la clase se activa justo antes de ejecutar el método: void ejecutate(Informacion i); El resultado de ejecutar este método es que se cambien los valores pertinentes de los items de información que se indiquen. Por ejemplo, si a la instancia que representa la asignación var := 5; 95 Sobre la información, su estructura y su-gestión se le lanza el mensaje ejecutate(i) lo que tiene que hacer es modificar el valor del ítem de información que represente a la variable var que se encuentra en la información i. El método que va a representar la desactivación de la instancia de la clase EjecuciónSentencia es: void fin(); Por tanto al finalizar la ejecución del método fin() se entenderá que el objeto está desactivado y ya no representa la ejecución de la sentencia. Evaluable El caso de los elementos evaluables es parecido. Partiendo de cada una de las instancias de aquellas clases del tiempo de compilación que implementen el interfaz Evaluable se ha de crear una clase que herede de la clase EjecucionEvaluacion. Las instancias activas de la clase EjecucionEvaluacion van a representar la evaluación. Se considera que la clase se activa justo antes de ejecutar el método: Valor evaluate(Informacion i); El resultado de ejecutar este método es que se cambien los valores pertinentes de los items de información que se indiquen y además, que se devuelva el valor resultado de la evaluación. Por ejemplo, si a la instancia que representa la llamada a esta función predefinida pred(5); se le lanza el mensaje evaluate(i) lo que tiene que hacer es ejecutar el código asociado a la función y devolver el valor 6. 96 Sobre la información, su estructura y su-gestión El método que va a representar la desactivación de la instancia de la clase EjecuciónSentencia es: void fin(); Por tanto al finalizar la ejecución del método fin() se entenderá que el objeto ya no representa la evaluación. Variables, Constantes y Parámetros. Items de Información Como hemos dicho antes, por cada variable, constante y parámetro que aparezca en un programa se tienen tiene que generar una clase hija de ItemDeInformacion. La clase ItemDeInformacion tiene un atributo de la clase Valor. Además tiene los métodos pertinentes para el acceso en lectura y escritura del valor. En las subclases de ItemDeInformacion que representen variables es posible que se lancen los métodos de lectura y escritura sobre el valor. En los ítems de información que representen a constantes, en tiempo de ejecución ya nos habremos asegurado de que en ningún momento se emplee el método de escritura. Para permitir el acceso al valor del ítem de información, la clase ItemDeInformación tiene implementados los siguiente métodos: Valor getValor(); void setValor(Valor v); El método que activa el ítem de información es: void creacion(Informacion i); Tendrá que hacer las acciones necesarias para que el ítem de información pase a formar parte de la información del programa. Además, si el ítem de 97 Sobre la información, su estructura y su-gestión información corresponde a una constante o parámetro, en este método se le asignará el valor pertinente. Conceptualmente, el ítem de información nace con el valor adecuado. La desactivación del ítem de información se realiza con el siguiente método. void destruccion(Informacion i); Se llevarán a cabo las acciones necesarias para que el ítem de información deje de formar parte de la información del programa. La clase ItemDeInformacion tiene tres hijas, cada una representa cada uno de los distintos tipos de ítems que hay. Variables La clase ItemVariable representa a las variables. Implementa el método creacion(Informacion i) para establecerse un valor aleatorio al construirse. Constantes La clase ItemConstante representa a las constantes. Implementa el método creacion(Informacion i) para establecerse el valor indicado en la constante. Parámetros La clase ItemParametro representa a los parámetros. Para crear un parámetro es necesario saber que valores han resultado de evaluar las expresiones de los parámetros actuales. En caso de los parámetros por referencia, también es necesario saber los ítems de información de los parámetros actuales. Por tanto el método de creación necesita de toda esa información. Se ha decidido añadir un nuevo parámetro al método para pasar esta información. El método creación en la clase ItemParametro tiene la forma: void creacion(Informacion i, ParametrosActuales pA); 98 Sobre la información, su estructura y su-gestión Existen dos tipos de parámetros, los parámetros por valor y los parámetros por referencia. Los parámetros por valor son aquellos que al crearse obtienen el valor del resultado de evaluar la expresión de su parámetro actual. Una vez que el ítem de información de un parámetro por valor se ha creado ya no tiene ninguna relación con el origen del valor. Cuando se ejecuta un subprograma que tiene un parámetro por referencia y dentro de ese subprograma se modifica el valor de ese parámetro, al finalizar la ejecución del subprograma aparece modificado el valor de la variable. Un ítem de información que representa a una variable que se pasa por referencia es el mismo ítem de información que se usa dentro del subprograma. Al ser el mismo ítem de información, el cambio de valor se ve reflejado al finalizar la ejecución del subprograma. Para poder representar que sea el “mismo” item el que represente al parámetro actual y al parámetro formal, según nuestra idea general, una objeto tendría que ser instancia de dos clases distintas, la clase ItemVariable y la clase ItemParametro. Y esto no es posible. Por tanto, tenemos que elegir una forma de representar conceptualmente esta idea en este modelo. Vamos a usar la idea de C de paso de parámetros por referencia. En C no existe el paso de parámetros por referencia. En C se obtiene esta funcionalidad pasando como parámetro por valor la dirección de la variable, es decir, la forma de acceder al ítem. La forma de implementarlo en nuestro modela va a ser usando punteros como en C. Veamos por encima como representamos un puntero en tiempo de ejecución. Básicamente un puntero es una variable convencional, lo que ocurre que su valor no es un valor como los demás, no es un 5, ni una ‘a’, su valor es la 99 Sobre la información, su estructura y su-gestión dirección de un item de información. En nuestro modelo no podemos hablar de dirección, en nuestro modelo lo que decimos es que el valor de una variable de tipo puntero permite acceder al item de información. Y para permitir acceder nos nos asociamos a él. En otras palabras, la referencia a un ítem de información se puede considerar como un valor convencional. En nuestro modelo representamos esta referencia con una relación de asociación, al igual que la asociación que tienen los ítems con los valores. En el modelo de ejecución un parámetro por referencia se comporta como el paso del item de información por valor. Referencias a Variables, Constantes y Parámetros. Por cada referencia a variable, constante y parámetro que aparezca en un programa se tienen tiene que generar una clase. Dependiendo del lugar donde se encuentra la referencia, será tratada como un elemento evaluable o como un elemento que sirve para acceder al item de información. Cuando la referencia es tratada como un elemento evaluable se aplica lo que ya hemos visto para estos elementos. Cuando se trata como un elemento que sirve para acceder al ítem de información se han de ofrecer los métodos para obtener esa funcionalidad, es decir, para acceder al ítem de información. Por tanto, cada referencia a variable, constante y parámetro que aparezca en un programa se tienen tiene que generar una clase hija de RefItemDeInformacion. Esta clase implementa EjecuciónEvaluación. Por tanto, tiene que implementar los métodos de EjecuciónEvaluación. Cuando se usen estos métodos de la instancia, conceptualmente significará que el elemento está siendo evaluado. Para acceder al ítem de información, el método elegido es: 100 Sobre la información, su estructura y su-gestión ItemDeInformacion getItemDeInformacion(Informacion i); Por tanto, cuando se use este método, conceptualmente significará que se está accediendo al ítem de información de entre toda la información del programa. Cuando se trate de un parámetro por referencia, como lo hemos modelado como un puntero, acceder al item de información asociado requiere de un nivel mayor de indirección. Igual ocurre cuando queramos obtener o asignar su valor, necesitamos de un nivel mayor de indirección. Valores Cualquier valor se va a representar como una instancia de la misma clase Valor. Que exista en memoria una instancia que represente el valor 3 significa que el valor 3 está siendo usando en alguna parte del proceso, bien asociado a un item de información o bien como resultado intermedio de la evaluación de una expresión. En algunas ocasiones, los valores aparecen en la parte evaluable del código. Por tanto, debería implementar el interfaz EvaluableEjecución para comportarse como un elemento evaluable en ejecución. Por este motivo, cuando se tiene que representar un valor en algún lugar evaluable del código no puede aparecer como la instancia de la clase valor simplemente, si no que se tiene que comportar como en elemento evaluable. Por este motivo, cuando aparece un valor en la parte evaluable se crea una clase que representa la evaluación del valor, pero no el valor en sí. El valor sigue representándose como una instancia de la clase valor. Un ejemplo básico 101 Sobre la información, su estructura y su-gestión Vamos a poner un ejemplo básico para repasar los conceptos introducidos en los apartados anteriores. Supongamos que tenemos el siguiente programa: PROGRAM EJEMPLO; VAR I:INTEGER; BEGIN I := 4; END. Hemos comentado que cada uno de los elementos de este programa se van a representar con una clase. Vamos a ver las clases que existen: • • • • • • Clase ProgramaEjemplo Clase VariableI Clase CuerpoProgramaEjemplo Clase SentenciaAsignacion Clase RefVariableI Clase AccesoValor4 Cada una de estas clases tiene que heredar, directa o indirectamente, de ItemDeInformación, EjecucionSentencia, EjecuciónEvaluación o RefItemDeInformacion. Veamos: • • • • • • Clase ProgramaEjemplo hereda de EjecucionSentencia Clase VariableI hereda de ItemDeInformacion Clase CuerpoProgramaEjemplo hereda de EjecucionSentencia Clase SentenciaAsignacion hereda de EjecucionSentencia Clase RefVariableI hereda de RefItemDeInformacion Clase EvaluacionValor4 hereda de EjecucionEvaluacion Vamos a ver que objetos se van activando y desactivando como resultado de la ejecución de este programa. Veamos lo que sucede: instante 1: Se instancia y activa un objeto de la clase ProgramaEjemplo, el programa se está ejecutando. 102 Sobre la información, su estructura y su-gestión instante 2: Se instancia y activa un objeto de la clase VariableI, existe un item de información en nuestro programa que corresponde a la variable I. instante 3: Se instancia y activa un objeto de la clase CuerpoProgramaEjemplo, se está ejecutando el cuerpo del programa. instante 4: Se instancia y activa un objeto de la clase SentenciaAsignacion, se está ejecutando la asignación. instante 5: Se instancia y activa un objeto de la clase EvaluacionValor4, se está evaluando el valor. instante 6: Se desactiva el objeto de la clase EvaluacionValor4, ya se ha evaluado el valor. instante 7: Se instancia y activa un objeto de la clase RefVariableI, se está accediendo al item de información de la variable I dentro de toda la información del programa. instante 8: Se desactiva el objeto de la clase RefVariableI, ya se tiene el item de información de la variable I. instante 9: Una vez que la asignación dispone de el valor resultado de evaluar su parte derecha y del item de información de su parte izquierda, asocia el valor al item. instante 10: Se desactiva el objeto de la clase SentenciaAsignacion, se ha terminado de ejecutar la sentencia de asignación. instante 11: Se desactiva el objeto de la clase CuerpoProgramaEjemplo, se ha terminado de ejecutar el cuerpo del programa. instante 12: Se desactiva el objeto de la clase VariableI, ya no existe el item de información de la variable I. instante 13: Se desactiva el objeto de la clase ProgramaEjemplo, se ha terminado de ejecutar el programa. Vamos a repetir el proceso pero entrando un poco mas en detalle. Vamos a ver que objetos se instancian, que métodos se ejecutan y que parámetros se pasan a cada método. 103 Sobre la información, su estructura y su-gestión instante 0: Se crea un objeto de la clase Información vacío, le llamamos i. No hay ninguna información en nuestro programa todavía. instante 1: Se crea un objeto de la clase ProgramEjemplo, le llamamos prog. instante 1.5: Se lanza el mensaje prog.ejecutate(i), se comienza la ejecución del programa. instante 2: Dentro del método ejecutate instancia un objeto de la clase VariableI, le llamamos variable. instante 2.5: Dentro del método ejecutate de prog se lanza el mensaje variable.creacion(i), se crea un item de información en nuestro programa que corresponde a la variable I. instante 3: Dentro del método ejecutate de prog se instancia un objeto de la clase CuerpoPrograma, le llamamos cuerpo. instante 3.5: Dentro del método ejecutate de prog se lanza el mensaje cuerpo.ejecutate(i), se comienza a ejecutar el cuerpo del programa. instante 4: Dentro del método ejecutate de cuerpo se instancia un objeto de la clase SentenciaAsignacion, le llamamos asignacion. instante 4.5: Dentro del método ejecutate de cuerpo se lanza el mensaje asignacion.ejecutate(i), se comienza a ejecutar la asignacion. instante 5: Dentro del método ejecutate de asignación se instancia un objeto de la clase EvaluaciónValor4, le llamamos v4. instante 5.3: Dentro del método ejecutate de asignación se lanza el mensaje v4.evaluate(i), el valor 4 se está evaluando. instante 5.6: El valor devuelto por el método se guarda temporalmente. instante 6: Dentro del método ejecutate de asignación se lanza el mensaje v4.fin(), ha finalizado la evaluación del valor. Instante 7: Dentro del método ejecutate de asignación se instancia un objeto de la clase RefVariableI, le llamamos referencia. instante 7.3: Dentro del método ejecutate de asignación se lanza el mensaje referencia.creacion(i), se está accediendo al item de información de la variable i. instante 7.6: El ítem devuelto por el método se guarda temporalmente. 104 Sobre la información, su estructura y su-gestión instante 8: Dentro del método ejecutate de asignación se lanza el mensaje referencia.fin(), ha finalizado el acceso a la variable. instante 9: Una vez que la asignación dispone de el valor resultado de evaluar su parte derecha y del ítem de información de su parte izquierda, asocia el valor al ítem. instante 9.5: Finaliza la ejecución del método ejecutate de asignación. instante 10: Dentro del método ejecutate de cuerpo se lanza el mensaje asignación.fin(), se ha terminado de ejecutar la sentencia de asignación. instante 10.5: Finaliza la ejecución del método ejecutate de cuerpo. instante 11: Dentro del método ejecutate de programa se lanza el mensaje cuerpo.fin(), se ha terminado de ejecutar el cuerpo del programa. instante 12: Dentro del método ejecutate de programa se lanza el mensaje variable.destruccion(i), ya no existe el ítem de información de la variable I. instante 12.5: Finaliza la ejecución del método ejecutate de programa. instante 13: Se lanza el mensaje programa.fin(), se ha terminado de ejecutar el programa. Queda por determinar cómo se define lo que ocurre en el instante 9, es decir, la semántica de las sentencias. La idea es que estas sentencias se modelen con autómatas, de esta forma, son formales. No obstante, hemos dejado este punto como otra de las vias posibles de ampliación de este TFC. Obtención de clases a partir de un código fuente Hemos marcado las pautas básicas para representar la ejecución de un programa usando la orientación a objetos. Cada elemento del código fuente se va a representar como una clase. Las instancias activas de esas clases representan ejecución, evaluación, etc… Vamos a automatizar el proceso de creación de esas clases partiendo de la representación en objetos del código fuente. Seguiremos la siguiente idea para 105 Sobre la información, su estructura y su-gestión automatizar el proceso: que cada objeto genere la clase que le representará en el modelo de ejecución. Hay objetos que no se van a representar en tiempo de ejecución, como los tipos. Estos objetos no tienen que generar ninguna clase. Vamos a hacer que cada objeto del tiempo de ejecución escriba la definición de su clase en modo texto en un flujo de salida. Todos los objetos del programa escribirán en un mismo flujo de salida. Ese flujo de salida representa un código fuente de Java. Una vez concluida la generación del código fuente en Java. Posteriormente, ese código fuente escrito en Java se compila y ya tenemos las clases que representan los elementos de un código fuente en particular. Si queremos representar su ejecución simplemente instanciamos un objeto de la clase que representa el programa principal y la activamos. 106 IV. Las innumerables ventajas y el supuesto inconveniente con sus innumerables desventajas En este capítulo vamos a mostrar las ventajas que aporta utilizar el patrón propuesto tanto para la construcción de herramientas como para la experimentación con los propios lenguajes. Hemos dividido el capítulo en varios apartados, cada uno centrándose en un conjunto de aplicaciones relacionadas. En los distintos apartados veremos cómo construir herramientas dedicadas a: • entornos de desarrollo de aplicaciones, • procesadores de lenguajes, • prototipado de lenguajes de programación, • ingeniería del software, • asistencia a la educación, y • interfaces gráficos de usuario. 107 Sobre la información, su estructura y su-gestión Aunque veamos varios apartados con aplicaciones del patrón totalmente dispares, la ventaja de que el patrón sea común para todos ellos, permite crear aplicaciones o herramientas que combinen varias de las posibilidades mostradas de forma sencilla. Por último, trataremos el omnipresente aspecto de la eficiencia. 1. Entornos de Desarrollo En la actualidad, para construir entornos de desarrollo se requiere la combinación de muchas tecnologías aparentemente muy dispares como analizadores léxicos, sintácticos, gestión de ficheros objeto de los códigos fuente, creación del interface de usuario, etc… Sin embargo, el poder usar una representación orientada a objetos de un código fuente permite la construcción de entornos de desarrollo mucho más rápida y cómoda al usar un patrón ya definido como el que proponemos: capas observadoras que recubren el modelo del lenguaje. Esto permite tratar al código fuente como un conjunto de objetos relacionados y podremos aplicar las técnicas de programación “convencionales” para el tratamiento del código fuente, y no acceder a él mediante complejos analizadores, conversores, etc… Veamos en distintas secciones más detalladamente las posibilidades que ofrece el patrón en la construcción de las partes de un entorno de desarrollo postergando para otro apartado los compiladores e intérpretes del lenguajes. Editores dirigidos por la sintaxis Al disponer de la información del modelo del lenguaje (o del metamodelo de un código de ese lenguaje, vease como dos caras de la misma moneda), transitivamente, se dispone de la información sobre las correctas relaciones de composición entre los elementos programáticos. Toda esta información conduce a la posibilidad de dirigir la edición del código correcto suministrando las 108 Sobre la información, su estructura y su-gestión posibilidades correctas en un punto dado y, en caso contrario, generación de avisos justificados ante ediciones incorrectas. Visualización del código Desde que se crearon los primeros lenguajes de programación, un código fuente se representa por un texto. Pese a que este texto mantenga una estructura sintáctica el programador “necesita” tener una visión mas geométrica y gráfica de lo que está programando. Por este motivo, un buen programador tiene la costumbre de sangrar su código, es decir, hacer que las líneas de código fuente comiencen un poco más a la derecha cada vez que se inicia un nivel de anidamiento programático para representar "gráficamente" la jerarquía de bloques. Esto obliga al programador al constante trasiego de líneas de código cuando ponemos o quitamos un nivel de anidamiento. Lamentablemente, sólo unos pocos entornos de desarrollo permiten que este recurso se gestione de forma semi-automática. Otra de las formas de representar un código fuente que usan los programadores es el resaltado de sintaxis mediante colores. Este recurso si que está ampliamente difundido en los entornos de desarrollo, y es bastante configurable, aunque realmente lo que realzan son las piezas léxicas. Sería interesante ampliar esta pobre capacidad de visualización del código que tienen los entornos de desarrollo. Si se usa el patrón propuesto para gestionar el código fuente, la visualización del código resultará una tarea muy sencilla. Ya en el prototipo mostrado vemos una aplicación de esta idea viendo una representación arbórea de un código fuente. Se podrían crear componentes específicos que permitiesen tener una parte del código de forma textual y otra parte del código en forma de árbol para poder hacer una gestión más efectiva del código fuente. Usando el patrón se permiten tener varias vistas simultáneas del mismo código fuente cada una con unas características diferentes. Incluso la personalización de colores, fuentes, ... por parte del usuario de cada una de estas vistas sería muy sencilla. 109 Sobre la información, su estructura y su-gestión Se podría incorporar una barra para controlar el “nivel de detalle” del código. Dependiendo del nivel de detalle requerido en cada momento se podrían mostrar líneas de código de más alto nivel o bien de más bajo nivel. Por ejemplo, un bajo nivel de detalle podría mostrar simplemente los nombres de las clases que se usan en un determinado paquete; con un nivel más alto, también se mostrarían de cada clase los nombre de los métodos y sus parámetros; otro nivel de detalle mostraría los atributos; otro nivel de detalle mostraría las descripciones de cada uno de los elementos de la clase; otro nivel de detalle mostraría una parte del código fuente, y así sucesivamente. No sería muy difícil que los niveles de detalle se aplicaran sólo a zonas concretas del código, por ejemplo sólo a un determinado método dejando los otros con solamente su descripción. Otro ejemplo de alto nivel de diferente naturaleza sería conmutar un pseudocódigo o descripcion textual dado por un comentario asociado a una expresión particular (p.e.: IF el caracter es vocal THEN ...) y su representación en código ejecutable (p.e.: IF (caracter='a') or (caracter='e') or (caracter='i') or (caracter='o') or (caracter='u') THEN ... ) Otras visualizaciones de código fuente más “clásicas” son aquellas que incorporan formas geométricas al código fuente en modo texto para ayudarte a localizar las partes relevantes. En Visual Basic entre los métodos de una clase aparece automáticamente una fina línea continua de separación. Esta idea se podría ampliar creando una línea vertical entre cada BEGIN y END de Pascal de forma que sea fácilmente reconocible a que BEGIN corresponde cada END y permitiendo una gestión más sencilla de los sangrados. La visualización idónea del código fuente sería un tema interesante de estudio que aquí no vamos a tratar. Lo que sí queremos dejar claro, es que usando el patrón propuesto, la creación y configuración de cualquiera de estas visualizaciones es muy sencilla; simplemente creando una clase vista que se encargue de la visualización de una clase del modelo, y jerarquizando estas 110 Sobre la información, su estructura y su-gestión clases vistas podemos crear visualizaciones como la del árbol vista en el prototipo. Depuradores La forma de crear un depurador partiendo del intérprete propuesto sería muy sencilla. Al estar cada objeto que representa la ejecución asociado a su objeto en el código fuente, hacer un seguimiento de la ejecución sería muy sencillo. Los puntos de parada se representarían mediante un atributo booleano en cada objeto que representa un elemento del código fuente y en el proceso de instanciación de los objetos de ejecución miraríamos ese valor booleano y, dependiendo de su valor, detendríamos o no la simulación. La monitorización del valor de las variables sería también muy sencillo e incluso podríamos ver todas las variables y objetos que actualmente están en memoria y qué valores tiene una misma variable dependiendo de la llamada recursiva. Podríamos crear expresiones que se evaluaran cuando quisiéramos, paradas dependiendo de condiciones, etc… Todo ello ampliando el modelo de objetos propuesto: más metodos y más clases sobre la misma jerarquía, previamente analizada y diseñada. Por contra, los depuradores actuales desarrollan un código intrusivo o software de andamiaje sobre un árbol de sintaxis abstracta desnaturalizado con una tabla de símbolo en tiempo de ejecución. Creación ortogonal de entornos de desarrollo Una vez dadas unas directivas generales de cómo debería de ser un entorno de desarrollo prototípico (editor, visualizador, compilador, depurador, ...) la creación de entornos de desarrollo para cualquier lenguaje de programación existente sería bastante sencilla, por no decir que automática. Esto es debido a que, como vimos en el primer capítulo, todos los lenguajes de programación tienen inmensos parecidos entre sí y los de un mismo paradigma aún más. Por 111 Sobre la información, su estructura y su-gestión tanto, definiendo un entorno prototípico genérico y especializado para cada uno de los paradigmas se podrían construir casi automáticamente entornos para cualquier lenguaje, tan sólo teniendo que especificar el modelo del lenguaje que queremos. Además, si se "programa/configura" una nueva forma de visualizar una sentencia alternativa if, de la que constan todos los lenguajes imperativos, esta nueva visualización se podría incorporar fácilmente en cualquier entorno de desarrollo de otro lenguaje. Esta forma de crear entornos de desarrollo permite mantener entornos de desarrollo homogéneos de diferentes lenguajes, que en definitiva, tienen prácticamente la misma funcionalidad. Por tanto, no ocurriría como ocurre actualmente que cuando queremos programar con dos lenguajes de programación, tenemos que aprender, además de dos lenguajes, dos entornos de desarrollo. Como se puede observar, la creación de un entorno de desarrollo multilenguaje no sería ningún problema, la única diferencia con un entorno de un solo lenguaje sería que en vez de trabajar con una sólo jerarquía de clases y objetos que modelan un solo lenguaje, se trabajaría con varias jerarquías que modelan diversos lenguajes. Programación visual Como hemos comentado antes, la forma de programar desde los primeros días de la programación es mediante texto. Si creamos un entorno de desarrollo que permita una visualización del código fuente de forma arbórea, eliminando aquellos elementos textuales necesarios para el analizador sintáctico y dejamos sólo los elementos clave del código, sería interesante, que el código se pudiese editar en esa misma representación gráfica. Es decir, sería interesante poder disponer de programación visual (aspecto diferente de la programación textual del interfaz gráfico de usuario). 112 Sobre la información, su estructura y su-gestión Con una representación orientada a objetos de un código fuente, la programación visual sería muy sencilla. Los objetos no los crearía un analizador sintáctico dependiendo de lo que se va encontrando en el texto de entrada como se ha propuesto en el prototipo. Simplemente, los objetos del código se crearían como acción asociada a botones que representen dichos elementos del código fuente; se podrían “arrastrar y soltar” bloques de código de un lugar a otro, básicamente sería quitar los bloques de donde estaban e insertarlos en el nuevo lugar; los mensajes a objetos se podrían hacer de forma visual eligiendo el objeto de entre los "elementos/iconos" accesibles en nuestro ámbito y, cuando le elijamos, poder seleccionar en otra ventana el método adecuado; es más, si queremos hacer lanzar un mensaje que corresponde con un método que todavía no existe, en la ventana donde aparezcan los métodos se podría crear su interfaz y luego más tarde se definiría. Mantener un modelo híbrido, en el que convivan la programación visual y textual tampoco es problema, ya que cada objeto que representa un elemento del programa, siempre podrá mostrarse en modo gráfico y posteriormente ser editado de forma convencional. Es un patrón recurrente de la programación orientada a objetos: modelo (elemento programático), controlador y vistas (vista textual, vista gráfica, ...) Como vemos, al tratar el problema del código fuente, en vez de cómo una secuencia de caracteres, como una estructura de objetos, muchos de los elementos que estamos comentando son muy sencillos, mientras que tratando el código fuente como una secuencia de caracteres serían impensables. Documentación del código Aparentemente un código fuente es símplemente un fichero en formato textual que representa, con mayor o menor nivel de abstracción, los datos que 113 Sobre la información, su estructura y su-gestión se manejan y cómo han de manejarse. Pero un código fuente es mucho más que eso. Los comentarios, pese a no ser una parte ejecutable o de definición de datos es un elemento importante del código fuente. Lo que ocurre es que generalmente, esta parte del código no presenta ninguna estructura y no se hace ninguna gestión con ellos. Sería interesante, poder dotar a los entornos de desarrollo de la capacidad de menjar los comentarios. JavaDoc es una herramienta propuesta por Sun para este propósito. Usando el modelo propuesto, la gestión de los comentarios no supone un cambio radical en el desarrollo del sistema, si no que símplemente se necesitaría añadir el atributo de la clase “comentario” a la clase más alta de la jerarquía para que todos los elementos del sistema fuesen susceptibles de ser comentados. De esta forma los comentarios se asociarían a los elementos del lenguaje, sirviendo para su documentación y no aparecerían “flotanto” desestructuradamente en el código como habitualmente se disponen. Otra parte importante es la gestión de versiones. Actualmente la gestión de versiones se basa en modificaciones en un fichero de texto y no se toman en cuenta los elementos del lenguaje para ver realmente que partes del código cambian de un código en una versión al código en la siguiente. Usando un sistema en el que se representan los elementos orientados a objetos, la gestión de versiones, apoyándose en un gestor de bases de datos orientadas a objetos, sería más sencilla de implementar y quizás más útil que basada en texto. Hay ocasiones en las que se escriben "a la vez" dos versiones del mismo programa. Por ejemplo hay veces que estamos construyendo programas que tienen trazas de depuración hasta que funcionan correctamente y cuando llega este momento ya no se necesitan, pero no se quitan por si fueran necesarias en un nuevo cambio en el código fuente. El soporte de este tipo de trazas es muy 114 Sobre la información, su estructura y su-gestión variado de unos lenguajes a otros y de unos entornos a otros. Por ejemplo en C estas trazas se crear mediante directivas de compilación para compilar o no ciertas líneas en el código. En LISP existe la función con efectos laterales TRACE. Como contrapartida, se podrían crear “capas” superpuestas de código. De manera que a la hora de generar un ejecutable se decida que capas se quieren compilar y que capas no. Crear esto con nuestro sistema sería bastante sencillo ya que se trataría de la simple gestión de datos orientados a objetos y no una compleja gestión de un compilador. 2. Procesadores de lenguajes En el prototipo mostrado, básicamente se construye una jerarquía de objetos a partir de un código fuente. La jerarquía de objetos representa el mismo programa que el código fuente; la diferencia es que la información está representada de forma distinta: estática/textual frente a dinámica/orientada a objetos. Para la construcción de procesadores (traductores, conversores o compiladores, e intérpretes) esta nueva representación es muy beneficiosa porque todas las tareas de análisis léxico y sintáctico propias de un procesador ya están realizadas usando este modelo. El cambio radical es que con este modelo se parte de un arbol de sintaxis abstracta que soporta sobre sí mismo toda la información de la tabla de simbolos, ya que todos los elementos que hacen referencia a otros dentro del código están debidamente “enlazados”. Creación de intérpretes Se acoge, tal y como se ha expuesto en el capítulo anterior, al modelado del lenguaje en una jerarquía de clases y su implantación interna basándose en la creación de objetos, enlaces y autómatas para dotar de la semántica del tiempo al modelo. 115 Sobre la información, su estructura y su-gestión Creación de compiladores La única labor del compilador sería la traducción a código máquina que podría realizarse con una capa observadora adicional (acogiendose al patrón expuesto en el capítulo anterior) que obtuviese los valores necesarios de los elementos del código fuente y volcase su traducción. Este volcado puede ser directamente en texto o bytes, si es sencilla una traducción directa, o bien puede ser realizado mediante la instanciación de una "secuencia" de objetos que represente el código ensamblador, y que sea posteriormente dicha "secuencia" de objetos la que se represente en bytes. Este paso intermedio sería necesario debido al gran hueco semántico entre los lenguajes de alto nivel y los lenguajes destino de los compiladores, de muy bajo nivel. Creación de conversores Partiendo de la base de que todos los lenguajes de programación se parecen muchísimo compartiendo elementos comunes, vamos a ver como sería una traducción de un programa escrito en un lenguaje a un programa escrito en otro lenguaje. Supongamos dos lenguajes, C y Pascal, del paradigma imperativo, comparten la inmensa mayoría de elementos, y sólo algunos que existen en C no existen en Pascal, por tanto, la estructura de objetos que represente el mismo código fuente en los dos lenguajes es prácticamente igual. Debido a esto, no sería necesario crear una jerarquía intermedia como en el caso de los compiladores, si no que la jerarquía sería prácticamente común en ambos lenguajes. Por tanto, se podrían crear conversores de lenguajes indicando simplemente como representar las abstracciones presentes en un lenguaje con las abstracciones presentes en otro lenguaje. El sistema podría automáticamente cambiar sólo aquellos elementos del lenguaje que cambien de un lenguaje a otro y dejar el resto igual, porque en definitiva, son iguales. La capa observadora 116 Sobre la información, su estructura y su-gestión encargada de la representación textual, se cambiaría al lenguaje destino y por tanto se representaría el código fuente como el lenguaje destino. Esta funcionalidad no tiene porque ser un proceso que se realice una vez finalizado un programa completo, si no que se podría incluir en el desarrollo del software, como una visualización más del mismo código. Optimizadores y des-optimizadores de código En muchas ocasiones, la claridad en el código fuente está reñida con la supuesta eficiencia de ese código fuente en ejecución. Los compiladores hacen muchas optimizaciones en el código para evitar esas situaciones. Se podrían crear optimizadores de alto nivel, que transformen un código fuente en otro, con la misma semántica, pero que evite ineficiencias. Todo ello obviando la manipulación de tercetos o cuartetos de bajo nivel, mediante el acceso a objetos de "alto nivel" con comportamiento. El ejemplo típico sería el de transformar el código: If (a > b) then aMayor = true; else aMayor = false; por el código: aMayor := (a > b); Pero es que en otras ocasiones, lo que se busca es lo contrario. Partir de un código eficiente pero altamente críptico para obtener un código menos eficiente pero más legible para un programador; el código C ofuscado, que es altamente usado por programadores convencionales, es representativo de esta situación. La creación de este tipo de optimizadores directos e inversos sería crear un nuevo código fuente partiendo de otro código fuente. Por tanto, sería cuestión 117 Sobre la información, su estructura y su-gestión de crear una capa observadora conversora, que obtenga los datos de la estructura de objetos y se encargue de mantener una estructura de objetos paralela. 3. Prototipado de lenguajes de programación La posibilidad de poder representar los elementos de un lenguaje de programación sin la engorrosa e inexpresiva formalidad sintáctica y mediante UML o una jerarquía de clases relacionadas mediante relaciones de asociación, composición y herencia sin duda aporta numerosas ventajas a la hora de estudiar el mundo relacionado con los lenguajes de programación. A lo largo de los siguientes apartados iremos viendo qué ventajas tenemos usando este modelado de los lenguajes de programación. Estudio comparativo entre lenguajes Al tener que representar los elementos de un lenguaje de programación mediante clases es mucho más fácil compararlos. Muchos lenguajes de programación usarán muchas clases comunes entre ellos, por ejemplo la sentencia condicional, la capacidad de llamada a una subrutina, la asignación, etc... En los lenguajes del mismo paradigma de programación estos parecidos son fáciles de reconocer, cosa que no ocurre en los lenguajes de distintos paradigmas. Por ejemplo, la modelización de Pascal y CAML usando las clases propuestas hace ver el tremendo parecido que tienen y estrictamente sus diferencias (p.e.: sentencia if de Pascal frente al operador if de CAML). La definición de los lenguajes mediante este modelado permitiría disipar muchas dudas que se plantean cuando se aprende un nuevo lenguaje de programación. 118 Sobre la información, su estructura y su-gestión Creación de lenguajes multiparadigmáticos Cuando se hace un estudio de lenguajes de distintos paradigmas se observa como tienen muchos conceptos en común. Por tanto si definimos un lenguaje que en algunas partes del código permite usar los elementos de un paradigma y en otras partes del código los elementos de otro paradigma habríamos creado un lenguaje multiparadigmático como Leda de Budd o C++ que es un lenguaje multiparadigmático porque permite programación orientada a objetos y programación procedural en un mismo código. Modelando nuestro propio lenguaje de programación podríamos juntar paradigmas tan dispares como el lógico y el orientado a objetos con la facilidad de elegir clases del repositorio de modelados de lenguajes pertenecientes a uno u otro paradigma. Creación de aplicaciones multilenguaje La creación de un lenguaje propio es una tarea ardua mirado desde un punto de vista tradicional, aunque usando los patrones propuestos se limita a pensar en el lenguaje y no en la forma de implantación. Otra utilidad sería disponer de un "multilenguaje" que en algunos momentos te permita escribir código LISP, por reutilización de código existente en bibliotecas, adecuación a la naturaleza de cierta parte de la aplicación (p.e. cálculo simbólico) y en otras partes del código te permite escribir código Java (p.e. bibliotecas de redes, multimedia, etc). Partiendo de un modelado de ambos lenguajes basado en UML, e indicando las conversiones de datos de un lenguaje a otro sería posible hacer aplicaciones multilenguaje de forma natural y sencilla evitando "puentes" (p.e.: código nativo de Java a C), "configuraciones" (p.e.: SQL embebido en COBOL), ... complejas e innecesarias. 119 Sobre la información, su estructura y su-gestión Solo habría que definir cómo deben coexistir ambas gramáticas, la forma en que se convertirían los datos intercambiables de un lenguaje a otro y la cesión del control de flujo de ejecución de un lenguaje a otro. Pero gracias a tener una base común para representar ambos lenguajes en el modelado, afrontar este proyecto sería abordable. Creación de meta-procesadores Como hemos observado a la hora de construir el modelo de Pascal en UML, simplemente hemos ido creando clases. Luego hemos asociado una gramática para la instanciación de objetos de esas clases en los momentos oportunos. Hemos creado una representación textual de cada uno de los elementos del lenguaje para que sea equivalente a la forma en que se reconoce dicho elemento. Lo que queda claro es que el modelado de un nuevo lenguaje de programación siguiendo el patrón propuesto se basa en seguir unas directivas en cuanto a representación de los conceptos. Es más, el modelado de C partiendo del modelado de pascal sería bastante sencillo. Por tanto, no sería muy complejo construir un sistema que ayude en la creación de lenguajes. Puede ofrecer todos aquellos mecanismos básicos, como la gestión de ámbitos, la asignación destructiva, la correspondencia de patrones, el algoritmo de unificación, la subprogramación, el manejo de excepciones y otros más avanzados como la orientación a objetos o la programción parametrizada. De esta forma, el creador de un lenguaje solo tiene que elegir aquellos mecanismos que le pueden ser útiles en la construcción de su lenguaje, desechar el resto y determinar la conjunción de los elegidos. La definición de la gramática puede también estar ayudada por un programa. Sólo tendremos que indicar que palabra reservada queremos usar para cada cosa, que token sería el adecuado para separar cada uno de los elementos de una lista y poco más. De forma automática se puede determinar 120 Sobre la información, su estructura y su-gestión las reglas gramáticales y con técnicas tradicionales, determinar la condición de LL(k) o LR(k), entre otros, para la generación automáticas de procesadores de éste lenguaje. Experimentación con lenguajes Con la construcción de un programa que permita crear nuevos lenguajes de programación aparece una posibilidad, antes impensable, de la experimentación con los lenguajes. Generalmente para crear un lenguaje de programación se estudia y se especifica claramente qué elementos se requieren en el lenguaje y en qué forma se van a expresar en texto. Una vez que se ha decidido y está todo perfectamente especificado se crea la gramática, el analizador léxico-sintáctico y luego se crea el compilador. Introducir un cambio a última hora en el lenguaje puede ser bastante engorroso por la cantidad de elementos que hay que cambiar. Si se crea un programa con el que se permita crear lenguajes de programación, el ciclo se acortaría, la gramática y la semántica no serían dos cosas totalmente disjuntas y se podría cambiar fácilmente elementos del lenguaje, quitar unos, poner otros, etc… en definitiva, se podría experimentar con los lenguajes. Podría estudiarse en un mismo lenguaje los 5 tipos de paso de parámetros (valor o entrada, entrada/salida, salida, variable, por nombre) alterando mínimamente el autómata de la implantación de las clases del modelo del lenguaje responsables del concepto del paso de parámetros. Otros aspectos posibilitados, al disponer del metamodelo en el sistema, sería la implantación de objetos re-clasificables en tiempo de ejecución (p.e. el propio objeto de la clase Empleado se comporta dinámicamente como objeto de la clase Jefe, sin necesidad de destrucción y nueva creación), jerarquías de clasificación 121 Sobre la información, su estructura y su-gestión multi-criteriales (p.e. un objeto se comporta como de la clase Vaca o Producto dependiendo del contexto); etc Meta-programación La metaprogramación es una potentísima herramienta que rara vez se usa para la construcción de aplicaciones, que permite altas cotas de desacoplamiento y, por tanto, reusabilidad. El soporte de metaprogramación es muy variado dependiendo del lenguaje de programación. En Basic se puede obtener el nombre de una variable como cadena de texto simplemente antecediendo a su nombre el carácter $. Es decir, se permite una metaprogramación de sólo lectura. En C la metaprogramación es inexistente. En LISP la metaprogramación es de lectura y escritura, es decir, en tiempo de ejecución se puede leer un código como una lista de elementos y se puede ejecutar una lista de elementos que representa un código. En Java la metaprogramación es de sólo lectura, aunque mucho más potente que la de Basic, pues permite la obtención de la clase de un objeto, y una vez obtenido la clase, preguntar su nombre, sus métodos etc… También se pueden lanzar mensajes mediante su nombre, definido en tiempo de ejecución, pero no se puede crear un código nuevo en tiempo de ejecución. El problema de la metaprogramación es la distinta forma en que los metadatos y las instrucciones se representan en ejecución. En nuestro modelo, eso es precisamente lo que hemos homogenizado, representamos los datos de un lenguaje orientado a objetos como el código fuente: objetos ambos del mismo modelo. 122 Sobre la información, su estructura y su-gestión 4. Ingeniería del Software La ingeniería del software se caracteríza, entre otras muchas cosas, por aportar mucha más información en el desarrollo del software que el propio código fuente. Otra parte importante en la ingeniería del software es que los programas que se desarrollen tengan determinada calidad en determinados aspectos. Por último, se podría decir que en ingeniería del software se estudian las pautas o patrones que han de seguir los programas para que sean reusables, desacoplados, etc... La gestión de información asociada al código fuente se simplifica mucho como hemos visto en el apartado anterior. Para determinar la calidad de un código fuente se dibujan gráficos de síntesis y se calculan medidas cauntitativas sobre el código. Veamos las facilidades que aporta el sistema propuesto en la creación de herramientas de ingeniería del software. Métricas del Software Debido a que cada elemento del programa se prepresenta como un objeto en una estructura de objetos, hacer medidas sobre el número de objetos que pertenecen a una determinada clase o que tienen unas características particulares es bastante sencillo. Sería básicamente aplicar los conocimientos de recorridos de árboles para inspeccionar todo el código fuente o recurrir a un sistemas gestor de bases de datos orientado a objetos para realizar las consultas de alto nivel deseadas. Es más, fácilmente se podría crear un patrones que permitiesen ocultar estos algoritmos de recorrido del árbol o consultas al gestor, dejando al usuario/programador la tarea de determinar qué cosas quiere medir. Patrones de diseño El uso de patrones, como hemos comentado más arriba, suponen una importante herramienta en la consrtucción de software de calidad. La detección 123 Sobre la información, su estructura y su-gestión automática de patrones facilita la documentación del código (para la legibilidad, el mantenimiento, la extensibilidad, ...) y caracteriza las cotas de calidad de la aplicación. Se pueden ver varios tipos de patrones en el desarrollo del software. Los patrones de bajo nivel son aquellas partes del código que se repiten habitualmente con la misma funcionalidad. Se podría considerar patrón de bajo nivel los usos que se hacen de una variable como contador, como switch, como acumulador, etc... La detección de estos patrones en un código fuente y la gestión de los mismos podría crearse en nuestro sistema. La cosa no sería tan sencilla como un simple contador de variables. Habría que aplicar ciertos conocimientos de inteligencia artificial en la búsqueda de patrones. La gestión de los mismos también sería un poco compleja debido a que no quedan claramente plasmados en el código. Pero sin duda, el problema sería abordable partiendo de una representación orientada a objetos del código fuente mediante la inspección de objetos declaración, expresión y sentencia. Los patrones de diseño actualmente se encuentran gestionados en aplicaciones de alto nivel. Lo que crea una separación a veces “insalvable” entre el diseño y la codificación. Al incorporar este tipo de funcionalidades a una herramienta de desarrollo se facilitaría el uso de estos patrones. Una forma de soportar esta característica en nuestro sistema sería que en una parte del sistema se pudiesen elegir o crear los patrones de diseño y que quedaran “asociados” al código que los implementa. De forma que a la hora de explorar el código sepamos a qué patrón o patrones se acoge cierto método, clase o grupo de instrucciones o bloques de código. La creación del “esqueleto” del código a partir de patrón del diseño se podría realizar de manera asistida. Para implementar esta capacidad en nuestro sistema habría que crear otras clases que representaran patrones de diseño y que se asociaran a las clases del código; de forma que, en un código fuente concreto, instancias de las clases de los patrones se asocien a instancias de las clases de los elementos del código fuente; 124 Sobre la información, su estructura y su-gestión donde algunas instancias del código fuente se podrían haber creado de forma asistida partiendo de las instancias de los patrones de diseño. Por último, están los patrones de alto nivel, aquellos patrones que se usan en el proceso de análisis. Sería un paso más en la jerarquía de patrones, de forma que estos patrones condicionarían el uso de los patrones de más bajo nivel. El esquema sería similar al comentado anteriormente pero en un nivel superior. El estudio más detallado de este aspecto supera los límites de este TFC. Visualización de diseño Hemos comentado que la ingeniería del software basa muchos de sus esfuerzos en encontrar representaciones gráficas apropiadas y expresivas de los patrones de alto y medio nivel que aparecen en el desarrollo del software. Para soportar esta característica en nuestro sistema tan sólo tendríamos que usar las técnicas genéricas de visualización basadas en el paradigma orientada a objetos ya que desde el código fuente hasta los patrones del alto nivel se representan usando esta herramienta. En el prototipo ya se ofrece una visualización jerárquica de los elementos de un código fuente. Para permitir visualización a nivel de diseño símplemente sería seguir ampliando el trabajo en esta área con gráficos de dependencia de bloques, etc. Lenguajes de diseño Hasta el momento se ha expuesto que el proceso de creación de lenguajes de programación usando nuestro sistema es más cómoda y abordable. Crear un lenguaje nuevo parecido a alguno existente sería muy sencillo. Por tanto, se abren vías para la creación de un lenguaje de programación que use las abstracciones usadas a nivel de diseño. Por ejemplo, basandonos en el lenguaje 125 Sobre la información, su estructura y su-gestión Java, se podrían incorporar palabras reservadas a los atributos de las clases para poder indicar relación de asociación o composición; el uso de las secuencias mediante una palabra reservada que denotase el concepto podríamos abstraer al usuario de la clase concreta usada para implementar dicha secuencia. Los programas escritos en este lenguaje de diseño no serían ejecutables eficientemente, pero en la construcción de prototipos aliviarían mucho la tarea del programador. Además, al ser un lenguaje de programación permitiría una traducción a otro lenguaje, por ejemplo Java, de forma asistida. En ese proceso de traducción se podrían tomar, de forma interactiva, las decisiones relativas a los detalles de implementación. Restricción de lenguajes En ingeniería del software normalmente se limitan las capacidades de los lenguajes de programación para hacer software de calidad. Por ejemplo, el número de parámetros de un método en Java es ilimitado, pero para crear programas de cálidad el número de parámetros no debe superar unos determinados umbrales. Esta forma de ampliar las restricciones semánticas que se imponen a un lenguaje se podrían configurar fácilmente usando nuestro sistema. La estructura orientada a objetos de un código fuente representa un código fuente válido estructuralmente con las referencias enlazadas a los elementos que referencias. Así, las comprobaciones semánticas son llevadas a cabo por nuevos objetos configurables asociados a cada uno de los objetos del código fuente. Para incluir nuestras propias restricciones se podría crear una nueva capa observadora. Esta capa observadora, que cubriría a todos los elementos del código fuente, sería la encargada de comprobar si los elementos del código fuente cumplen nuevas restricciones, de forma que la compilación se haría en varias fases. Algunas 126 Sobre la información, su estructura y su-gestión fases serían obligatorias y otras fases serían recomendables para crear software de calidad aunque no serían obligatorias para crear el programa. Documentación de programas La documentación de los programas es también una parte fundamental de los productos entregados por la aplicación bajo un método de ingeniería del software. Este punto está muy relacionado con la gestión de los comentarios del código fuente anterior. Sin embargo, sería como documentar también los patrones de diseño y los patrones de análisis. En ocasiones es útil obtener la documentación de los programas en formatos como XML, HTML, documentos de Word, etc... para la distribución por la vías “convencionales”. Por tanto se requiere que a partir de un código fuente y sus comentarios asociados se transformen en estas otras representaciones. La labor de transformación la llevaría a cabo otra capa observadora que obtuviese la información necesaria de los objetos que representan el código fuente y la trataría para obtener de ella XML, HTML, PDF, etc... 5. Asistencia a la educación Hemos comentado anteriormente que este sistema se podría usar para construir entornos de desarrollo equivalentes para distintos lenguajes de programación. Como se expuso anteriormente, se podría crear un entorno de desarrollo que permitiese desarrollar código en distintos lenguajes de programación. Este entorno de desarrollo multilenguaje sería muy bien recibido en el área de educación dado que a lo largo de una carrera se enseñan multitud de lenguajes de programación, lo que obliga al alumno a estar constantemente aprendiendo como usar las herramientas asociadas a cada lenguaje de programación y le hace desviarse del objetivo principal, aprender a usar el lenguaje de programación. En este sentido la aplicación del sistema propuesto 127 Sobre la información, su estructura y su-gestión sería bastante útil. Existen otros espectos en educación en los que el uso del sistema propuesto aporta cosas útiles. Veremos cada uno de ellos en los siguientes apartados. Visualización del software Se entiende por visualización del software la representación gráfica de los conceptos y abstracciones presentes en la ejecución del software. De una forma un poco menos formal se podría decir que la visualización del software es dibujar las variables, la ejecución de las instrucciones, las estructuras de datos a medida que se va ejecutando un programa o algoritmo. La visualización del software es un recurso objeto de estudio desde hace muchos años que se empieza a usar en la enseñanza. Una de las grandes dificultades en el uso de la visualización del software es su creación. El desarrollo de representaciones gráficas de los programas en ejecución no es una tarea sencilla. Además, generalmente las visualizaciones se crean específicamente para un determinado algoritmo, es decir, es complejo crear un sistema genérico y automático de visualización de cualquier programa. Con el uso de nuestro sistema, la construcción de visualización del software es una tarea tremendamente sencilla. Los elementos de un lenguaje de programación se representan por objetos y el proceso de interpretación de un código fuente también se traduce en la instanciación de objetos. Por tanto, para crear la visualización del proceso de ejecución de un código, símplemente tendríamos que usar las técnicas habituales de representación gráfica orientadas a objetos. 128 Sobre la información, su estructura y su-gestión Lenguajes adaptativos La programación orientada a objetos como evolución de las técnicas anteriores de programación a traído más reusabilidad, modularidad, abstracción, etc... al desarrollo del software. Ahora los profesores de las carreras de informática se plantean cuando es el momento adecuado de enseñar a programar orientado a objetos. Existe una gran corriente de profesores innovadores que piensan que la enseñanza de la programación se debería hacer de manera progresiva. Esta enseñanza se basaría en la evolución que han sufrido los lenguajes de programación y la justificación que ha ido propiciando dicha evolución, para que el propio alumno sea consciente de que la tecnología orientada a objetos no es el último paradigma y sepan encontrar en él los puntos débiles que le harán evolucionar al cabo de los años. La implantación de esta idea tiene algunos problemas prácticos. Se acepta ámpliamente que para enseñar a programar es necesario un lenguaje de programación con el que los alumnos ejecuten sus diseños. Si enseñamos a programar de manera evolutiva, necesitaremos que nuestro lenguaje de programación vaya evolucionando a la par que las explicaciones. Este lenguaje evolutivo en las fases tempranas, cercanas a la programación en ensamblador tendría instrucciones de tipo GOTO. En la última fase tendría el concepto de clase. Los códigos fuente escritos en el lenguaje usando las herramientas de las primeras fases, no compilarían cuando el lenguaje se encuentre en la fase orientada a objetos. No tenemos que olvidar que ciertas herramientas del lenguaje, no han cambiado desde el momento que aparecieron, como por ejemplo las expresiones, de forma que el lenguaje debería soportarlas sin variación desde que aparecen. Construir un entorno de desarrollo que permita soportar esta idea sería bastante duro desde el punto de vista tradicional. Habría que construir varios compiladores a la vez que compartan algunas partes y en otras partes difieran. 129 Sobre la información, su estructura y su-gestión El uso de nuestro sistema aportaría numerosas facilidades en la construcción del sistema. El estudio pormenorizado de la creación de este sistema supera los límites de este TFC. Lenguajes parciales Otra necesidad imperiosa en la docencia de un lenguaje es posibilitar la experimentación con partes del lenguaje sin obligar a la edición de un programa completo. Por ejemplo, en la enseñanza/aprendizaje de la expresiones o definición de registros, disponer del lenguaje parcial que aborde únicamente los aspectos a tratar. Estas vistas parciales del lenguaje permitiría la creación de herramientas didácticas que determinen la corrección sintáctica y semántica de partes aisladas de un programa, facilitando el acercamiento a los niveles cognitivos iniciales de la taxonomía de Bloom (compresión, aplicación y análisis). 6. Interfaces gráficos de usuario La creación de los interfaces gráficos de usuario ha evolucionado bastante con la llegada de la tecnología orientada a objetos. De hecho, la difusión al gran público de la tecnología orientada a objetos se ha introducido por los entornos de desarrollo como VisualBasic y Delphi y su modelo de componente software. Una aplicación con una buena interfaz gráfica representa gráficamente las opciones y servicios disponibles. Una típica aplicación para windows tiene varios menús con opciones, agrupadas por funcionalidad. Poseen una barra de acceso rápido con elementos que también son accesibles a través de los menús y luego el elemento o elementos gestionados por el programa. En un programa tipo Word, el elemento es un documento de texto. En una aplicación de gráficos, los elementos son gráficos. En una aplicación basada en base de datos los elementos gestionados son informes y ventanas de acceso y búsqueda. A 130 Sobre la información, su estructura y su-gestión muy grandes rasgos, la interfaz gráfica es un reflejo de lo que está ocurriendo por dentro en la propia aplicación, de la lógica y la estructura de la información. La interfaz gráfica es una representación del estado y acciones de nuestros programas. En ingeniería del software, las técnicas de análisis basadas en prototipos es crear pantallas de nuestro futuro programa y enseñárselas al cliente. El cliente no sabe programar, pero con las pantallas sabe la información que gestiona en el programa y las formas que hay de acceder a ella. Por tanto, podríamos estudiar más a fondo la manera de crear el interfaz gráfico de usuario partiendo de las clases de nuestro programa de manera asistida. Este estudio mostraría una problemática asociada que, de nuevo, no es objetivo pero posibilita la propuesta de este TFC: el uso del sistema propuesto para representar el código fuente y la posibilidad de añadir patrones de diseño y análisis es un buen punto de partida para comenzar dicho estudio. 7. La eficiencia, el inconveniente y sus innumerables desventajas El sistema propuesto adolece en un punto frente a los métodos tradicionales de construcción de compiladores e intérpretes, la eficiencia. Los compiladores tradicionales, no representan en memoria el código fuente, si no que a medida que le van analizando de la cadena de caracteres realizan todo el trabajo posible. Lo hacen para reducir la cantidad de memoria exigida y para ser lo mas rápidos posibles. El sistema propuesto en su parte de interpretación también es muy poco eficiente, ya que está constantemente instanciando objetos para representar cosas insignificantes del código. Por tanto sabemos que nuestro sistema es más lento y consume más memoria. La interpretación se hace principalmente por motivos de visualización del software, por experimentación en la creación de nuevos lenguajes y para 131 Sobre la información, su estructura y su-gestión depuración en partes específicas del código. Obviamente para estas funciones el punto menos crítico es la eficiencia. En visualización del software las personas tienen que ver el desarrollo, lo cual hace que tenga que ser lento. En la experimentación con lenguajes la interpretación va a requerir un seguimiento exhaustivo que no requiere de mucha velocidad. En cuanto a la depuración en determinadas partes del código, generalmente se reduce a una pequeña parte del código que es admisible que sea algo más lenta. Por otro lado, la capacidad de interpretación no es parte fundamental del sistema propuesto: se puede eliminar del sistema en la versión de la aplicación para explotación tras su desarrollo. Se puede sustituir por un traductor a código máquina o a un lenguaje de programación que posteriormente se compile. Por ejemplo, a nuestro prototipo se le podría incorporar la capacidad de traducción a C++. Tras la traducción se invocaría el compilador de C++ y tendríamos nuestro eficiente programa ejecutable. En el caso de lenguajes necesariamente interpretados (p.e. Lisp, Java o Smalltalk por características de metaprogramación o portabilidad entre otras) se pueden generar la representaciones intermedias adecuadas para la eficiencia (p.e. grafos simbólicos para Lisp o bytecodes para Java). El uso de memoria ya no es un problema. Las técnicas de construcción de compiladores tradicionales no han cambiado en muchos años. Antes la memoria era un bien escaso que había que gestionar bajo condiciones extremas. Actualmente, con las posibilidades de memoria virtual de los sistemas operativos y la gran cantidad de memoria de cualquier ordenador convencional ya no es prohibitivo el uso de mucha memoria. Además, en la construcción de un entorno de desarrollo integrado, si nuestro compilador estuviese basado en las técnicas tradicionales, sería la parte que menos recursos necesitaría. Dicho de otra forma, no tiene sentido hacer un compilador altamente eficiente, con un uso de memoria mínimo y que requiera un gran tiempo de desarrollo, si luego 132 Sobre la información, su estructura y su-gestión el propio interfáz gráfico del entorno va a ocupar muchísima más memoria que el propio compilador. En cuanto a la velocidad de compilación es muy discutible que se viera afectada. Actualmente el elemento mínimo de compilación es el fichero de texto. De forma que si cambiamos una solo línea en un fichero fuente hay que recompilar todo el fichero fuente. Con unas pocas mejoras en nuestro sistema, la creación de objetos que representan los elementos del código, se haría solamente para los elementos que cambian y no para todo el texto fuente, lo que reduciría enormemente el tiempo de compilación. Es más, con el sistema propuesto, la creación de objetos y la comprobación semántica se podría hacer en segundo plano a medida que el usuario teclea el programa. Los errores podrían aparecer subrayados en rojo instantes después de haber escrito la línea, lo que sin duda haría que el usuario los corrigiese mucho más deprisa que si tiene que volver a esa línea después de haber escrito muchas más y haber ejecutado el compilador. Por supuesto no tenemos que olvidar la programación visual. Muy sencilla de añadir a nuestro sistema y que posiblemente reduciría el tiempo de análisis léxico-sintáctico porque ya no hay texto que analizar. Es posible que la programación visual en todas las fases de desarrollo de un programa aumente el tiempo de desarrollo total. Pero la combinación de la programación visual y la programación textual reduciría los tiempos totales de desarrollo. Lo cual hace que la velocidad del compilador sea un punto indiferente si se consigue que se desarrollen los programas más rápido. Ya hemos visto que no supone ningún problema que nuestra forma para construir un compilador sea ineficiente. Vemos que se puede usar sin inconvenientes. Por tanto veamos ahora que desventajas supondría mantener el compilador usando las técnicas tradicionales. 133 Sobre la información, su estructura y su-gestión Las herramientas para la construcción de compiladores o intérpretes sólo generan el analizador léxico-sintáctico, por lo tanto, las rutinas semánticas hay que programarlas para cada nuevo lenguaje que creemos. Por tanto el desarrollo se hace más largo y complejo. La incorporación de cualquier traducción diferente de la que se programara inicialmente supondría modificar partes del código existente y acoplar totalmente todas las traducciones, lo que hace que el código tenga menos calidad por ser más complejo de depurar y con más posibilidades de que la parte que funcionaba deje de hacerlo. La búsqueda de elementos concretos del código con consultas del estilo “las variables con el nombre X dentro de cierto ámbito“ sería prácticamente inviable. Podríamos seguir enumerando muchas funciones que serían muy difíciles de obtener usando los métodos tradicionales de creación de compiladores. 134 V. Conclusiones Considero que se han cumplido los objetivos de este Trabajo Fin de Carrera. Se han sentado las bases para la construcción de un sistema que modela un lenguaje de programación. Se han puesto de manifiesto las grandes posibilidades de usar el sistema propuesto. A partir de aquí se abren muchas posibilidades. Se podría hacer un Trabajo Fin de Carrera por cada uno de los apartados del capítulo 4. Lo que sería muy interesante, porque permitiría comprobar empíricamente cómo las ideas tienen aplicación. Para finalizar la exposición de este Trabajo Fin de Carrera mostramos un prototipo del modelo propuesto. Hay que destacar que el prototipo no es una implementación directa y funcional del modelo propuesto. El prototipo que mostramos ha servido para experimentar y tomar decisiones sobre el modelo. Por este motivo, existen puntos del modelo propuesto que no se ven reflejados en el prototipo. Por contra, ciertas partes del prototipo no se han incluido en el modelo. Esto se debe a que hay ciertos elementos que consideramos que son detalles de una implementación particular. Otro motivo que hace que en el 135 Sobre la información, su estructura y su-gestión prototipo aparezcan partes no comentadas en el modelo es por considerar que son temas que no hemos estudiado con la profundidad suficiente debido a la esfuerzo que supondría y que está fuera del ámbito de este Trabajo Fin de Carrera. 136 VI. Bibliografía Fernández, L.; Arroyo, F. Programación Orientada a Objetos. Publicaciones de la E.U.I., 1998 Frutos, J.A; López, J.; Pérez, J.G. Teoría de Lenguajes de Programación. Publicaciones de la E.U.I., 1998 Pérez, J.G. Implementación de un Subconjunto de Pascal. Publicaciones de la E.U.I., 1998 Stasko, J.; Domingue, J.; Brown, M.H.; y Price, B.A. (eds.), Software Visualization: Programming as a Multimedia Experience, MIT Press, 1998. Novática, Marzo — Abril 2001 de ATI. http://www.ati.es/novatica/ Budd, T. Introducción a la Programación Orientada a Objetos. Addison Wesley, 1994 Santos, E.; García, A.; Alonso, S.; Alarcón, P.P.; Garbajosa, J; Bases de Datos. Publicaciones de la E.U.I. 1998 Booch, G.; Jacobson, I.; Rumbaugh, J.; El Lenguaje Unificado de Modelado. Addison Wesley, 1999 Booch, G.; Jacobson, I.; Rumbaugh, J.; El lenguaje unificado de modelado. Manual de referencia. Addison Wesley, 1999 137 Sobre la información, su estructura y su-gestión Booch, G.; Jacobson, I.; Rumbaugh, J.; El proceso unificado de desarrollo de software. Addison Wesley, 1999 Fowler, M.; Scott, K; UML Gota a Gota. Addison Wesley, 1999 Schildt, H.; Ansi C a su alcance. Mc Graw Hill, 1991 Barros, B.; Foulquie, M.T.; Serradilla, F.; Programación Funcional en LISP. Publicaciones de la E.U.I., 1992 Fernández, L.; Arroyo, F.; Yela, A.; Programación Funcional. Programación en CAML. Publicaciones de la E.U.I., 1997 Tannenbaum, A; Metadata Solutions: Using Metamodels, Repositories, XML, and Enterprise Portals to Generate Information on Demand. Addison Wesley, 2002 Fayyad, U; Grinstein, G.; Wierse, A.; Information Visualization in Data Mining and Knowledge Discovery. 2001 Détienne, F.; Software design - cognitive aspects. Frank Bott, 2002 Dorsey, P.; Hudicka, J.; Oracle8 Database Design Using UML Object Modeling. Osborne McGraw-Hill (Oracle Press), 1998 Santos, E.; Estructuras de datos-I, Publicaciones de la E.U.I, 2000 Campione, M.; Walrath, K.; Huml, A; The Java(TM) Tutorial: A Short Course on the Basics (3rd Edition). Addison-Wesley, 2000 Walrath, K.; Campione, M.; The JFC Swing Tutorial: A Guide to Constructing GUIs. Addison-Wesley, 1999 Eckstein, R.; Loy, M.; Wood, D.; Java Swing. O'Reilly & Associates, 1998 Fernández, L; Tecnología Orientada a Objetos aplicada al desarrollo de la Traducción de Lenguajes. Proyecto Fin de Carrera. Universidad de Málaga, 1999 Joyanes, L.; Fundamentos de Programación. Algoritmos y Estructuras de Datos. McGraw-Hill, 1988 Hurtado, T.; Ingeniería del software. Publicaciones E.U.I., 1995 Jaworski, J.; Java 1.2 Al descubierto. Prentice Hall, 1998 Zaks, R.; Programación en Pascal, Turbo Pascal. Anaya Multimedia, 1986 138 Sobre la información, su estructura y su-gestión Joyanes Aguilar, L.; Programación en Turbo-Borland Pascal 7. Osborne- McGraw-Hill, 1997 Fernández, L.; 185 enunciados resueltos en Pascal. Publicaciones E.U.I., 1995 Borland International; Turbo Pascal, version 6.0 Programmer's guide, Scotts Valley : Borland, 1990 Grogono, P.; Programming in Pascal, Addison-Wesley, 1984 Lakshman, B.; Oracle and Java development, Sams, cop. 2002 Arnold, K.; El lenguaje de programación Java, Pearson Educación, 2001 139 Anexo A. Fuentes package tfc; public interface AccesoAmbito { public GestorAmbito getAmbito(); } package tfc; import javax.swing.UIManager; import java.awt.*; public class AppPrueba2 { boolean packFrame = false; //Construct the application public AppPrueba2() { Frame2 frame = new Frame2(); //Validate frames that have preset sizes //Pack frames that have useful preferred size info, e.g. from their layout if (packFrame) { frame.pack(); } else { frame.validate(); } //Center the window Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); Dimension frameSize = frame.getSize(); if (frameSize.height > screenSize.height) { frameSize.height = screenSize.height; } if (frameSize.width > screenSize.width) { frameSize.width = screenSize.width; } frame.setLocation((screenSize.width - frameSize.width) / 2, (screenSize.height - frameSize.height) / 2); frame.setVisible(true); } 140 Sobre la información, su estructura y su-gestión //Main method public static void main(String[] args) { try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch(Exception e) { e.printStackTrace(); } new AppPrueba2(); } } package tfc; import java.util.*; import java.io.PrintWriter; public class Asignacion extends ElementoCodigo implements Sentencia { protected RefVariable parteIzquierda; protected Evaluable parteDerecha; public Asignacion(RefVariable parteIzquierda, Evaluable parteDerecha) { this.parteIzquierda = parteIzquierda; this.parteDerecha = parteDerecha; } public RefVariable getParteIzquierda(){ return parteIzquierda; } public Evaluable getParteDerecha() { return parteDerecha; } public String getRepTextual(){ return parteIzquierda.getRepTextual() + " := " + parteDerecha.getRepTextual(); } public void resuelveReferencias(GestorAmbito ambito, ErroresReferencias errores){ parteIzquierda.resuelveReferencias(ambito,errores); parteDerecha.resuelveReferencias(ambito,errores); } private int codigo; public void convertirEnEjecutable(PrintWriter salida, GestorIdentificadores gestor){ this.codigo = gestor.getCodigo(); this.parteIzquierda.convertirEnEjecutable(salida,gestor); this.parteDerecha.convertirEnEjecutable(salida,gestor); salida.println("//------------------------------------------------------------------------"); salida.println("class "+this.getNombreEjecucion()+" extends AsignacionEjecucion {"); salida.println(" public EvaluableEjecucionI getParteDerecha(){"); salida.println(" return new "+parteDerecha.getNombreEjecucion()+"(); "); salida.println(" }"); salida.println(" public RefVariableEjecucion getRefVariableEjecucion() {"); salida.println(" return new "+parteIzquierda.getNombreEjecucion()+"(); "); salida.println(" }"); 141 Sobre la información, su estructura y su-gestión salida.println("}"); } public String getNombreEjecucion(){ return "AsignacionEjecucion"+codigo; } } package tfc; import java.io.PrintWriter; public abstract class AsignacionEjecucion extends InstruccionEjecucion { public void ejecuta(InformacionAccesible i, PrintWriter salida){ salida.println("Inicio ejecución asignación"); salida.println("Inicio evaluación parte derecha"); Valor valor = this.getParteDerecha().evaluate(i,salida); salida.println("Fin evaluación parte derecha, el valor es:"+valor.getRepTextual()); this.getRefVariableEjecucion().getItemDeInformacion(i).setValor( valor , salida); salida.println("Fin ejecución asignación"); } public abstract EvaluableEjecucionI getParteDerecha(); public abstract RefVariableEjecucion getRefVariableEjecucion(); } package tfc; import javax.swing.tree.*; public class AsignacionTreeNode extends InstruccionTreeNode { Asignacion asignacion; RefVariableTreeNode parteIzquierda; EvaluableTreeNodeI parteDerecha; public ElementoCodigoInterfaz getElementoCodigo(){ return asignacion; } public AsignacionTreeNode(Asignacion asignacion) { this.setModeloRecursivo(asignacion); } public AsignacionTreeNode(){ setCabecera("Asignacion -> "); } public int getIndexOfChild(Object child){ if (child == parteIzquierda){ return 0; } else { return 1; } } 142 Sobre la información, su estructura y su-gestión public void valueForPathChanged(TreePath path, Object newValue){ // De momento no se permite la edición. } public boolean isLeaf(){ return false; } public int getChildCount(){ return 2; } public Object getChild(int index){ switch (index) { case 0: return parteIzquierda; case 1: return parteDerecha; default: return null; } } public void setModelo(Object asignacion){ this.asignacion = (Asignacion) asignacion; } public void setModeloRecursivo(Object obj){ this.setModelo(obj); Class clase; Class claseTreeNode; try { clase = asignacion.getParteIzquierda().getClass(); claseTreeNode = Class.forName( clase.getName() + "TreeNode" ); parteIzquierda = (RefVariableTreeNode)claseTreeNode.newInstance(); parteIzquierda.setModeloRecursivo( asignacion.getParteIzquierda() ); clase = asignacion.getParteDerecha().getClass(); claseTreeNode = Class.forName( clase.getName() + "TreeNode" ); parteDerecha = (EvaluableTreeNodeI)claseTreeNode.newInstance(); parteDerecha.setModeloRecursivo( asignacion.getParteDerecha() ); } catch (ClassNotFoundException e){ System.out.println(e.getMessage()); e.printStackTrace(); } catch (Exception e){ System.out.println(e.getMessage()); } } } package tfc; public class Booleano extends Enumerado { static Booleano yo = null; public static Booleano getInstance() { if (yo == null){ yo = new Booleano(); } return yo; } private Booleano(){ 143 Sobre la información, su estructura y su-gestión this.nombre = new Nombre("boolean"); this.representacion = "Verdadero o Falso, si o no"; valores.add( new ValorOrdinal("true",this)); valores.add( new ValorOrdinal("false",this)); } public Valor getValorAleatorio(){ return new ValorSimple("true",this); } } package tfc; public class Caracter extends Enumerado { static Caracter yo = null; public static Caracter getInstance() { if (yo == null){ yo = new Caracter(); } return yo; } private Caracter(){ this.setNombre(new Nombre("char")); this.representacion = "Caracteres"; for(int i=0; i<256;i++){ valores.add( new ValorOrdinal("/'"+Character.forDigit(i,10)+"/'",this)); } } public Valor getValorAleatorio(){ return new ValorSimple("a",this); } } package tfc; public class CargadorClaseDirectorioTrabajo extends ClassLoader { public CargadorClaseDirectorioTrabajo() { super(); } // Busca la clase en el directorio de trabajo public Class findClass(String name) { byte[] b = loadClassData(name); return defineClass(name, b, 0, b.length); } private byte[] loadClassData(String name) { try { java.io.File ficheroF = new java.io.File(System.getProperty("user.dir")+ java.io.File.separator+name+".class"); java.io.FileInputStream fichero = new java.io.FileInputStream(ficheroF); byte[] bytes = new byte[ (int)ficheroF.length() ]; fichero.read(bytes); fichero.close(); 144 Sobre la información, su estructura y su-gestión return bytes; } catch(Exception e){ e.printStackTrace(); } return null; } } package tfc; import java.io.*; public class CompiladorJava { String rutaCompilador; public CompiladorJava(String rutaCompilador) { this.rutaCompilador = rutaCompilador; } // Compila el programa pasado en el String y le deja compilado en el directorio // indicado. La clase debe pertenecer al paquete por defecto. El directorio // indicado será añadido al PATH para poder ejecutar la clase mediante reflection. // Para que posteriormente funcione el reflection, el classpath debe estar puesto // en el directorio actual (de trabajo). public Class compila(String programa, String nombreClase) throws ErrorCompilacionException { File f = new File(System.getProperty("user.dir") + File.separator + nombreClase+".java"); if (f.exists()){ f.delete(); } boolean excepcion = false; String resultado = ""; try{ f.createNewFile(); java.io.PrintWriter writer = new java.io.PrintWriter( new java.io.FileOutputStream(f)); f.createNewFile(); writer.print( programa ); writer.flush(); writer.close(); Process proceso = Runtime.getRuntime().exec(rutaCompilador+" \""+System.getProperty("user.dir") + File.separator + nombreClase+".java\""); LineNumberReader flujoSalida = new LineNumberReader( new InputStreamReader( new java.io.BufferedInputStream(proceso.getInputStream()))); LineNumberReader flujoError = new LineNumberReader( new InputStreamReader( new java.io.BufferedInputStream(proceso.getErrorStream()))); 145 Sobre la información, su estructura y su-gestión do { String salida = flujoSalida.readLine(); if (salida == null) { break; } resultado += salida+"\n"; } while (true); do { String salida = flujoError.readLine(); if (salida == null) { break; } resultado += salida+"\n"; } while (true); excepcion = !resultado.equals(""); } catch( Exception e){ e.printStackTrace(); } if (excepcion) { throw new ErrorCompilacionException(resultado); } else { try { ClassLoader cargador = new CargadorClaseDirectorioTrabajo(); return cargador.loadClass(nombreClase); } catch(Exception e2){ e2.printStackTrace(); } } return null; } } package tfc; public class Condicional extends ElementoCodigo implements Sentencia { Evaluable evaluable; Sentencia parteThen = null; Sentencia parteElse = null; public Condicional(Evaluable evaluable, Sentencia ejecutable) { this.evaluable = evaluable; this.parteThen = ejecutable; } public void setParteElse(Sentencia parteElse){ this.parteElse = parteElse; } public void resuelveReferencias(GestorAmbito ambito, ErroresReferencias errores) { evaluable.resuelveReferencias(ambito,errores); parteThen.resuelveReferencias(ambito,errores); if (parteElse != null) parteElse.resuelveReferencias(ambito,errores); } public String getRepTextual(){ String resultado = ""; resultado += "IF " + evaluable.getRepTextual() + " THEN \n"; resultado += parteThen.getRepTextual(); resultado += (parteElse == null)?"": "ELSE\n" + parteElse.getRepTextual(); return resultado; } 146 Sobre la información, su estructura y su-gestión public Evaluable getEvaluable(){ return evaluable; } public Sentencia getParteThen(){ return parteThen; } public Sentencia getParteElse(){ return parteElse; } public void convertirEnEjecutable(java.io.PrintWriter salida, GestorIdentificadores gestor){ // POR HACER } } package tfc; import java.io.PrintWriter; public abstract class CondicionalEjecucion extends InstruccionEjecucion { public void ejecuta(InformacionAccesible i, PrintWriter salida){ salida.println("Inicio ejecucion if"); salida.println("Inicio evalacion expresion"); Valor v = this.evaluaExpresion(i); salida.println("Fin evaluacion expresion. Valor: "+v.getRepTextual()); // Ojo, este .equals debería de ser un == pero por comodidad se hace equals if ( v.equals( Booleano.getInstance().getValor("true") ) ){ salida.println("Inicio ejecucion parte then"); this.ejecutaParteThen(i); salida.println("Fin ejecucion parte then"); } else { salida.println("Inicio ejecucion parte else"); this.ejecutaParteElse(i); salida.println("Fin ejecucion parte else"); } } public abstract Valor evaluaExpresion(InformacionAccesible i); public abstract void ejecutaParteThen(InformacionAccesible i); public abstract void ejecutaParteElse(InformacionAccesible i); } package tfc; import javax.swing.tree.TreePath; public class CondicionalTreeNode extends InstruccionTreeNode { Condicional condicional; EvaluableTreeNodeI evaluable; EjecutableTreeNodeI parteThen; EjecutableTreeNodeI parteElse = null; public CondicionalTreeNode() { } public boolean isLeaf() { 147 Sobre la información, su estructura y su-gestión return false; } public Object getChild(int parm1) { switch (parm1) { case 0: return evaluable; case 1: return parteThen; default: return parteElse; } } public void setModeloRecursivo(Object obj) { this.setModelo(obj); evaluable = (EvaluableTreeNodeI)TreeNode.getTreeNode( condicional.getEvaluable() ); evaluable.setCabecera("Expresion -> "); parteThen = (EjecutableTreeNodeI)TreeNode.getTreeNode( condicional.getParteThen() ); parteThen.setCabecera("Then "); Sentencia parteElseAux = condicional.getParteElse(); if (parteElseAux != null){ parteElse = (EjecutableTreeNodeI)TreeNode.getTreeNode( parteElseAux ); parteElse.setCabecera("Else "); } } public int getIndexOfChild(Object parm1) { if (parm1 == evaluable){ return 0; } else if (parm1 == parteThen){ return 1; } else { return 2; } } public int getChildCount() { return (parteElse == null)? 2: 3; } public void valueForPathChanged(TreePath parm1, Object parm2) { } public ElementoCodigoInterfaz getElementoCodigo() { return condicional; } public void setModelo(Object obj) { this.condicional = (Condicional)obj; } } package tfc; public class DefConstante extends ElementoCodigo implements Definicion, ElementoCodigoEjecucion { protected Nombre nombre; protected Valor valor; public DefConstante( Nombre nombre, Valor valor ) { this.nombre = nombre; this.valor = valor; } public Nombre getNombre(){ return nombre; } public String getRepTextual(){ 148 Sobre la información, su estructura y su-gestión return nombre+":"+valor.getRepTextual(); } public Valor getValor(){ return valor; } public void resuelveReferencias(GestorAmbito ambito, ErroresReferencias errores){ // POR HACER } public void convertirEnEjecutable(java.io.PrintWriter salida, GestorIdentificadores gestor){ this.setCodigo(gestor); this.getValor().convertirEnEjecutable(salida,gestor); salida.println("class "+this.getNombreEjecucion()+" extends ItemDeInformacion {"); salida.println(" public "+this.getNombreEjecucion()+"(){"); salida.println(" }"); salida.println(" public void creacion(java.io.PrintWriter salida){ "); salida.println(" salida.println(\"Reservada memoria constante\");"); salida.println(" this.setValor( new "+this.getValor().getNombreEjecucion()+"().evaluate(null,salida),salida);"); salida.println(" }"); salida.println("}"); } } package tfc; public abstract class DefConstanteEjecucion extends Ejecucion { Valor valor; public void creacion(){ valor = this.getValor(); } public Valor getValor(){ return valor; } } package tfc; import javax.swing.tree.*; public class DefConstanteTreeNode extends TreeNode { DefConstante defConstante; NombreTreeNode nombre; ValorSimpleTreeNode valor; public ElementoCodigoInterfaz getElementoCodigo(){ return defConstante; } public DefConstanteTreeNode(DefConstante defConstante) { this.setModeloRecursivo(defConstante); } public DefConstanteTreeNode() { this.setCabecera("Definicion de Constante -> "); } 149 Sobre la información, su estructura y su-gestión public void setModelo(Object defConstante){ this.defConstante = (DefConstante) defConstante; } public int getIndexOfChild(Object child){ if (child == nombre){ return 0; } else { return 1; } } public void valueForPathChanged(TreePath path, Object newValue){ // De momento no se permite la edición. } public boolean isLeaf(){ return false; } public int getChildCount(){ return 2; } public Object getChild(int index){ switch (index) { case 0: return nombre; case 1: return valor; default: return null; } } public void setModeloRecursivo(Object obj){ this.setModelo(obj); nombre = (NombreTreeNode)TreeNode.getTreeNode(defConstante.getNombre()); valor = (ValorSimpleTreeNode)TreeNode.getTreeNode(defConstante.getValor()); } public String toString(){ return defConstante.toString(); } } package tfc; public class DefFuncion extends DefSubprograma { protected RefTipo tipoRetorno; public DefFuncion(Nombre s, AccesoAmbito ambitoSuperior){ super(s,ambitoSuperior); } public DefFuncion(){} public void setTipo(RefTipo refTipo){ this.tipoRetorno = refTipo; } protected String getRTCabecera(){ // El tipo de retorno va al final de la declaracion, pero de momento lo pongo // así por comodidad. return "FUNCTION "+ this.tipoRetorno.getRepTextual() + ":"+ this.getNombre(); } 150 Sobre la información, su estructura y su-gestión protected void setTipoRetorno(RefTipo tipoRetorno){ this.tipoRetorno = tipoRetorno; } public DefTipo getDefTipo(){ return tipoRetorno.getDefTipo(); } } package tfc; public abstract class DefFuncionEjecucion extends DefSubprogramaEjecucion { Valor v; public DefFuncionEjecucion() { } public Valor getValor(){ return v; } protected void setValor(Valor v){ this.v = v; } } package tfc; public abstract class DefFuncionOperador extends DefFuncion { public DefFuncionOperador(Nombre n){ super(n,null); } } package tfc; public class DefFuncionOperadorAnd extends DefFuncionOperador { public DefFuncionOperadorAnd() { super(new Nombre("AND")); this.getParametros().addElement( new DefParametro(new Nombre("a"),new RefTipo(Booleano.getInstance()))); this.getParametros().addElement( new DefParametro(new Nombre("b"),new RefTipo(Booleano.getInstance()))); this.setTipo( new RefTipo( new Nombre("booleano"))); } } package tfc; public class DefFuncionOperadorDivision extends DefFuncionOperador { public DefFuncionOperadorDivision() { super(new Nombre("DIV")); this.getParametros().addElement( new DefParametro(new Nombre("a"),new RefTipo(Entero.getInstance()))); this.getParametros().addElement( new DefParametro(new Nombre("b"),new RefTipo(Entero.getInstance()))); this.setTipo( new RefTipo( new Nombre("integer"))); } } package tfc; 151 Sobre la información, su estructura y su-gestión public class DefFuncionOperadorIgualdad extends DefFuncionOperador { public DefFuncionOperadorIgualdad() { super(new Nombre("=")); this.getParametros().addElement( new DefParametro(new Nombre("a"),new RefTipo(Entero.getInstance()))); this.getParametros().addElement( new DefParametro(new Nombre("b"),new RefTipo(Entero.getInstance()))); this.setTipo( new RefTipo( new Nombre("boolean"))); } } package tfc; public class DefFuncionOperadorMod extends DefFuncionOperador { public DefFuncionOperadorMod() { super(new Nombre("MOD")); this.getParametros().addElement( new DefParametro(new Nombre("a"),new RefTipo(Entero.getInstance()))); this.getParametros().addElement( new DefParametro(new Nombre("b"),new RefTipo(Entero.getInstance()))); this.setTipo( new RefTipo( new Nombre("integer"))); } } package tfc; public class DefFuncionOperadorMultiplicacion extends DefFuncionOperador { public DefFuncionOperadorMultiplicacion() { super(new Nombre("*")); this.getParametros().addElement( new DefParametro(new Nombre("a"),new RefTipo(Entero.getInstance()))); this.getParametros().addElement( new DefParametro(new Nombre("b"),new RefTipo(Entero.getInstance()))); this.setTipo( new RefTipo( new Nombre("integer"))); } } package tfc; public class DefFuncionOperadorNegacion extends DefFuncionOperador { public DefFuncionOperadorNegacion() { super(new Nombre("-")); this.getParametros().addElement( new DefParametro(new Nombre("a"),new RefTipo(Entero.getInstance()))); this.setTipo( new RefTipo( new Nombre("integer"))); } } package tfc; public class DefFuncionOperadorNot extends DefFuncionOperador { public DefFuncionOperadorNot() { super(new Nombre("NOT")); this.getParametros().addElement( new DefParametro(new Nombre("a"),new RefTipo(Booleano.getInstance()))); this.setTipo( new RefTipo( new Nombre("boolean"))); } } package tfc; public class DefFuncionOperadorOr extends DefFuncionOperador { 152 Sobre la información, su estructura y su-gestión public DefFuncionOperadorOr() { super(new Nombre("OR")); this.getParametros().addElement( new DefParametro(new Nombre("a"),new RefTipo(Booleano.getInstance()))); this.getParametros().addElement( new DefParametro(new Nombre("b"),new RefTipo(Booleano.getInstance()))); this.setTipo( new RefTipo( new Nombre("boolean"))); } } package tfc; public class DefFuncionOperadorResta extends DefFuncionOperador { public DefFuncionOperadorResta() { super(new Nombre("-")); this.getParametros().addElement( new DefParametro(new Nombre("a"),new RefTipo(Entero.getInstance()))); this.getParametros().addElement( new DefParametro(new Nombre("b"),new RefTipo(Entero.getInstance()))); this.setTipo( new RefTipo( new Nombre("integer"))); } } package tfc; public class DefFuncionOperadorSuma extends DefFuncionOperador { public DefFuncionOperadorSuma() { super(new Nombre("+")); this.getParametros().addElement( new DefParametro(new Nombre("a"),new RefTipo(Entero.getInstance()))); this.getParametros().addElement( new DefParametro(new Nombre("b"),new RefTipo(Entero.getInstance()))); this.setTipo( new RefTipo( new Nombre("integer"))); } } package tfc; public class DefFuncionTreeNode extends DefSubprogramaTreeNode { public DefFuncionTreeNode() { } public DefFuncionTreeNode(DefFuncion defFuncion) { super(defFuncion); } } package tfc; public interface Definicion { public Nombre getNombre(); } package tfc; public class DefinicionNoEncontradaException extends Exception { Nombre nombre; String tipoDefinicion; public DefinicionNoEncontradaException(Nombre nombre, String tipoDefinicion) { 153 Sobre la información, su estructura y su-gestión this.nombre = nombre; this.tipoDefinicion = tipoDefinicion; } public String toString(){ return "No se ha encontrado un "+tipoDefinicion+" con el nombre \""+nombre.toString()+"\"."; } } package tfc; public class DefParametro extends DefVariable { private boolean pasoPorValor; public DefParametro(Nombre nombre, RefTipo refTipo) { this(nombre,refTipo,true); } public boolean pasoPorValor(){ return pasoPorValor; } public DefParametro(Nombre nombre, RefTipo refTipo,boolean pasoPorValor) { super(nombre,refTipo); this.pasoPorValor = pasoPorValor; } public String getRepTextual(){ String resultado = super.getRepTextual(); if (!pasoPorValor){ resultado = "VAR "+resultado; } return resultado; } public void convertirEnEjecutable(java.io.PrintWriter salida, GestorIdentificadores gestor){ this.setCodigo(gestor); salida.println("class "+this.getNombreEjecucion()+" extends ItemDeInformacionParam {"); salida.println(" public "+this.getNombreEjecucion()+"(){}"); salida.println("}"); } }---package tfc; public class DefProcedimiento extends DefSubprograma { public DefProcedimiento(Nombre s, AccesoAmbito ambitoSuperior){ super(s,ambitoSuperior); } public DefProcedimiento(Nombre s){ super(s); } protected String getRTCabecera(){ return "PROCEDURE "+ this.getNombre(); } public void convertirEnEjecutable(java.io.PrintWriter salida, GestorIdentificadores gestor){ this.setCodigo(gestor); this.subprogramas.convertirEnEjecutable(salida,gestor); this.constantes.convertirEnEjecutable(salida,gestor); this.variables.convertirEnEjecutable(salida,gestor); //this.parametros.convertirEnEjecutable(salida,gestor); 154 Sobre la información, su estructura y su-gestión int codigoInformacion = InformacionAccesible.convertirEnEjecutable(salida,gestor,constantes,parametros,vari ables); this.cuerpo.convertirEnEjecutable(salida,gestor); salida.println("//---------------------------------------------------------------------------------------"); salida.println("class "+this.getNombreEjecucion()+" extends DefProcedimientoEjecucion {"); salida.println(" public "+this.getNombreEjecucion()+"() {}"); salida.println(" public InformacionAccesible getInformacionAccesible(InformacionAccesible ambitoSuperior, java.io.PrintWriter salida){"); salida.println(" InformacionAccesible i = new InformacionAccesible"+codigoInformacion+"();"); salida.println(" i.creacion(ambitoSuperior,salida);"); salida.println(" return i;"); salida.println(" }"); salida.println(" public InformacionAccesible getInformacionAccesible(ParametrosActualesValoresEItems p, InformacionAccesible inf, java.io.PrintWriter salida){"); salida.println(" InformacionAccesible i = new InformacionAccesible"+codigoInformacion+"();"); salida.println(" i.creacion(p,inf,salida);"); salida.println(" return i;"); salida.println(" }"); salida.println(" public void ejecutaInstrucciones(InformacionAccesible i, java.io.PrintWriter salida){"); salida.println(" EjecutableEjecucionI e;"); salida.println(" e = new "+this.cuerpo.getNombreEjecucion()+"();"); salida.println(" e.ejecuta(i,salida);"); salida.println(" e.fin();"); salida.println(" }"); salida.println("}"); salida.println("//---------------------------------------------------------------------------------------"); } } package tfc; public abstract class DefProcedimientoEjecucion extends DefSubprogramaEjecucion { public DefProcedimientoEjecucion() { } } package tfc; public class DefProcedimientoTreeNode extends DefSubprogramaTreeNode { public DefProcedimientoTreeNode() { } public DefProcedimientoTreeNode(DefProcedimiento defProcedimiento) { super(defProcedimiento); } } package tfc; import javax.swing.*; import javax.swing.event.*; public class DefPrograma extends ElementoCodigo implements Definicion, AccesoAmbito, ElementoCodigoEjecucion { 155 Sobre la información, su estructura y su-gestión protected protected protected protected protected protected protected Nombre nombre; SecuenciaDefConstante constantes; SecuenciaDefVariable variables; SecuenciaDefSubprograma subprogramas; SecuenciaDefTipo tipos; InstruccionCompuesta cuerpo; GestorAmbito ambito; protected DefPrograma(){} public DefPrograma(Nombre nombre){ this.nombre = nombre; this.constantes = new SecuenciaDefConstante(); this.variables = new SecuenciaDefVariable(); this.subprogramas = new SecuenciaDefSubprograma(); this.tipos = new SecuenciaDefTipo(); this.cuerpo = new InstruccionCompuesta(); setAmbito(new GestorAmbito(tipos,variables,constantes,subprogramas,new SecuenciaDefParametro(),new Predefinidos())); } public GestorAmbito getAmbito(){ return ambito; } public void setCuerpo(InstruccionCompuesta cuerpo){ this.cuerpo = cuerpo; } protected void setAmbito(GestorAmbito ambito){ this.ambito = ambito; } public SecuenciaDefConstante getConstantes(){ return constantes; } public SecuenciaDefVariable getVariables(){ return variables; } public SecuenciaDefSubprograma getSubprogramas(){ return subprogramas; } public SecuenciaDefTipo getTipos(){ return tipos; } public InstruccionCompuesta getCuerpo(){ return cuerpo; } public String getRepTextual() { String resultado =""; resultado += getRTCabecera() + '\n'; resultado += getRTCuerpo() + "."; return resultado; } protected String getRTCabecera(){ return "PROGRAM "+ this.nombre +";"; } protected String getRTCuerpo(){ String resultado = ""; 156 Sobre la información, su estructura y su-gestión for(int i=0; i<subprogramas.getSize(); i++) { resultado += ((DefSubprograma)subprogramas.getElementAt(i)).getRepTextual() + ";"+'\n'; } if (tipos.size() != 0){ resultado += "TYPE"+'\n'; for(int i=0; i<tipos.getSize(); i++) { resultado += ((DefTipo)tipos.getElementAt(i)).getRepTextual() + ";"+'\n'; } } if (constantes.size() != 0){ resultado += "CONST"+'\n'; for(int i=0; i<constantes.getSize(); i++) { resultado += ((DefConstante)constantes.getElementAt(i)).getRepTextual() + ";"+'\n'; } } if (variables.size() != 0){ resultado += "VAR"+'\n'; for(int i=0; i<variables.getSize(); i++) { resultado += ((DefVariable)variables.getElementAt(i)).getRepTextual() + ";"+'\n'; } } for(int i=0; i<constantes.getSize(); i++) { resultado += ((DefSubprograma)subprogramas.getElementAt(i)).getRepTextual() + ";"+'\n'; } resultado += cuerpo.getRepTextual(); return resultado; } public Nombre getNombre(){ return nombre; } public void convertirEnEjecutable(java.io.PrintWriter salida, GestorIdentificadores gestor){ salida.println("import tfc.*;"); this.setCodigo(gestor); this.subprogramas.convertirEnEjecutable(salida,gestor); this.constantes.convertirEnEjecutable(salida,gestor); this.variables.convertirEnEjecutable(salida,gestor); int codigoInformacion = InformacionAccesible.convertirEnEjecutable(salida,gestor,constantes,new SecuenciaDefParametro(),variables); this.cuerpo.convertirEnEjecutable(salida,gestor); salida.println("//---------------------------------------------------------------------------------------"); salida.println("public class "+this.getNombreEjecucion()+" extends DefProgramaEjecucion {"); salida.println(" public "+this.getNombreEjecucion()+"() {}"); salida.println(" public InformacionAccesible getInformacionAccesible(InformacionAccesible ambitoSuperior, java.io.PrintWriter salida){"); 157 Sobre la información, su estructura y su-gestión salida.println(" InformacionAccesible i = new InformacionAccesible"+codigoInformacion+"();"); salida.println(" i.creacion(ambitoSuperior,salida);"); salida.println(" return i;"); salida.println(" }"); salida.println(" public void ejecutaInstrucciones(InformacionAccesible i, java.io.PrintWriter salida){"); salida.println(" EjecutableEjecucionI e;"); salida.println(" e = new "+this.cuerpo.getNombreEjecucion()+"();"); salida.println(" e.ejecuta(i,salida);"); salida.println(" e.fin();"); salida.println(" }"); salida.println("}"); salida.println("//---------------------------------------------------------------------------------------"); } public ErroresReferencias resuelveReferencias(){ ErroresReferencias errores = new ErroresReferencias(); this.resuelveReferencias(ambito,errores); return errores; } public void resuelveReferencias(GestorAmbito ambitoSuperior, ErroresReferencias errores){ System.out.println("Ambito del programa "+this.getNombre().toString()+"\n"+ambito.getRepTextual()); constantes.resuelveReferencias(ambito,errores); variables.resuelveReferencias(ambito,errores); tipos.resuelveReferencias(ambito,errores); subprogramas.resuelveReferencias(ambito,errores); cuerpo.resuelveReferencias(ambito,errores); } } package tfc; import java.io.PrintWriter; public abstract class DefProgramaEjecucion extends Ejecucion implements EjecutableEjecucionI { public void ejecuta(InformacionAccesible inf, PrintWriter salida){ salida.println("Inicio ejecución programa"); InformacionAccesible i = this.getInformacionAccesible(new InformacionAccesiblePredefinida(), salida); this.ejecutaInstrucciones(i,salida); i.liberacion(salida); salida.println("Fin ejecución programa"); } public void fin(){ } public abstract InformacionAccesible getInformacionAccesible(InformacionAccesible ambitoSuperior, java.io.PrintWriter salida); public abstract void ejecutaInstrucciones(InformacionAccesible i, java.io.PrintWriter salida); } 158 Sobre la información, su estructura y su-gestión package tfc; import import import import javax.swing.tree.*; javax.swing.event.*; java.lang.reflect.*; java.util.HashMap; public class DefProgramaTreeModel implements TreeModel { protected DefProgramaTreeNode defProgramaModelo2; protected EventListenerList listenerList = new EventListenerList(); public DefProgramaTreeModel(DefPrograma defPrograma) { this.defProgramaModelo2 = new DefProgramaTreeNode(defPrograma); } public void addTreeModelListener(TreeModelListener l) { listenerList.add(TreeModelListener.class, l); } public void removeTreeModelListener(TreeModelListener l) { listenerList.remove(TreeModelListener.class, l); } public int getIndexOfChild(Object parent, Object child){ return ((TreeNode)parent).getIndexOfChild(child); } public void valueForPathChanged(TreePath path, Object newValue){ } public boolean isLeaf(Object node){ return ((TreeNode)node).isLeaf(); } public int getChildCount(Object parent){ return ((TreeNode)parent).getChildCount(); } public Object getChild(Object parent, int index){ return ((TreeNode)parent).getChild(index); } public Object getRoot(){ return defProgramaModelo2; } } package tfc; import javax.swing.tree.*; public class DefProgramaTreeNode extends TreeNode { DefPrograma defPrograma; NombreTreeNode nombre; SecuenciaDefTipoTreeNode tipos; SecuenciaDefConstanteTreeNode constantes; SecuenciaDefVariableTreeNode variables; SecuenciaDefSubprogramaTreeNode subprogramas; InstruccionCompuestaTreeNode cuerpo; public DefProgramaTreeNode(DefPrograma defPrograma) { this.setModeloRecursivo(defPrograma); 159 Sobre la información, su estructura y su-gestión } public DefProgramaTreeNode() { } public ElementoCodigoInterfaz getElementoCodigo(){ return defPrograma; } public boolean isLeaf() { return false; } public Object getChild(int index) { switch (index) { case 0: return nombre; case 1: return tipos; case 2: return constantes; case 3: return variables; case 4: return subprogramas; case 5: return cuerpo; default: return null; } } public int getIndexOfChild(Object child) { if (child == nombre){ return 0; } else if (child == tipos){ return 1; } else if (child == constantes){ return 2; } else if (child == variables){ return 3; } else if (child == subprogramas){ return 4; } else if (child == cuerpo){ return 5; } return -1; } public int getChildCount() { return 6; } public void valueForPathChanged(TreePath path, Object newValue) { } public void setModelo(Object obj) { this.defPrograma = (DefPrograma) obj; } public void setModeloRecursivo(Object obj){ this.setModelo(obj); nombre = new NombreTreeNode(defPrograma.getNombre()); nombre.setCabecera("Nombre: "); tipos = new SecuenciaDefTipoTreeNode(defPrograma.getTipos()); tipos.setCabecera("Tipos"); constantes = new SecuenciaDefConstanteTreeNode(defPrograma.getConstantes()); constantes.setCabecera("Constantes"); variables = new SecuenciaDefVariableTreeNode(defPrograma.getVariables()); variables.setCabecera("Variables"); subprogramas = new SecuenciaDefSubprogramaTreeNode(defPrograma.getSubprogramas()); subprogramas.setCabecera("Subprogrmas"); 160 Sobre la información, su estructura y su-gestión cuerpo = new InstruccionCompuestaTreeNode(defPrograma.getCuerpo()); cuerpo.setCabecera("Cuerpo"); } public String toString(){ return "Programa"; } } package tfc; import javax.swing.DefaultListModel; public abstract class DefSubprograma extends DefPrograma implements Definicion { protected SecuenciaDefParametro parametros; protected DefSubprograma(){} protected DefSubprograma(Nombre nombre, AccesoAmbito ambitoSuperior){ this.nombre = nombre; this.constantes = new SecuenciaDefConstante(); this.variables = new SecuenciaDefVariable(); this.subprogramas = new SecuenciaDefSubprograma(); this.tipos = new SecuenciaDefTipo(); this.cuerpo = new InstruccionCompuesta(); GestorAmbito ambito = null; if (ambitoSuperior != null) { ambito = ambitoSuperior.getAmbito(); } setAmbito(new GestorAmbito(tipos,variables,constantes,subprogramas,new SecuenciaDefParametro(),ambito)); this.parametros = new SecuenciaDefParametro(); } protected DefSubprograma(Nombre s){ super(s); this.parametros = new SecuenciaDefParametro(); } public SecuenciaDefParametro getParametros(){ return parametros; } protected String getRTParametros(){ String resultado = ""; resultado += "("; if (parametros.getSize() > 0) { for (int i=0; i < parametros.getSize() - 1; i++){ resultado += ((DefParametro) parametros.getElementAt(i)).getRepTextual()+";"; } resultado += ((DefParametro) parametros.lastElement()).getRepTextual(); } resultado += ");"; return resultado; } public String getRepTextual() { String resultado =""; resultado += getRTCabecera() + " " + getRTParametros() + ";"+'\n'; resultado += getRTCuerpo(); return resultado; } 161 Sobre la información, su estructura y su-gestión public boolean compatible(ParametrosActuales p){ return p.compatible(parametros); } public void resuelveReferencias(GestorAmbito ambitoSuperior, ErroresReferencias errores){ System.out.println(this.getNombre().toString()+"\n"+ambito.getRepTextual()); // this.ambito.superior = ambitoSuperior; constantes.resuelveReferencias(ambito,errores); variables.resuelveReferencias(ambito,errores); tipos.resuelveReferencias(ambito,errores); subprogramas.resuelveReferencias(ambito,errores); cuerpo.resuelveReferencias(ambito,errores); } } package tfc; import java.io.PrintWriter; public abstract class DefSubprogramaEjecucion extends DefProgramaEjecucion { public DefSubprogramaEjecucion() { } public void ejecuta(InformacionAccesible i, PrintWriter salida){ this.ejecuta(null,i,salida); } public void ejecuta(ParametrosActualesValoresEItems p, InformacionAccesible inf, PrintWriter salida){ salida.println("Inicio ejecución procedimiento"); InformacionAccesible i = this.getInformacionAccesible(p,inf,salida); this.ejecutaInstrucciones(i,salida); i.liberacion(salida); salida.println("Fin ejecución procedimiento"); } public abstract InformacionAccesible getInformacionAccesible(ParametrosActualesValoresEItems p, InformacionAccesible inf, PrintWriter salida); public abstract void ejecutaInstrucciones(InformacionAccesible i, PrintWriter salida); public abstract InformacionAccesible getInformacionAccesible(InformacionAccesible ambitoSuperior, PrintWriter salida); } package tfc; public class DefSubprogramaTreeNode extends DefProgramaTreeNode { public DefSubprogramaTreeNode(DefSubprograma defSubprograma) { super(defSubprograma); } public DefSubprogramaTreeNode() { } } package tfc; 162 Sobre la información, su estructura y su-gestión public abstract class DefTipo extends ElementoCodigo implements Definicion { protected Nombre nombre; protected String representacion; public Nombre getNombre(){ return nombre; } protected void setNombre(Nombre nombre){ this.nombre = nombre; } } package tfc; public abstract class DefTipoPredefinido extends DefTipo { } package tfc; // Sin implementar todavía import javax.swing.tree.TreePath; public class DefTipoTreeNode extends TreeNode { DefTipo defTipo; public ElementoCodigoInterfaz getElementoCodigo(){ return defTipo; } public DefTipoTreeNode(DefTipo defTipo) { this.defTipo = defTipo; setCabecera("Definicion de Tipo ->"); } public DefTipoTreeNode() { setCabecera("Definicion de Tipo ->"); } public boolean isLeaf() { return true; } public Object getChild(int index) { return null; } public void setModeloRecursivo(Object obj) { } public int getIndexOfChild(Object child) { return -1; } public int getChildCount() { return 0; } public void valueForPathChanged(TreePath path, Object newValue) { } public void setModelo(Object obj) { } } package tfc; public class DefVariable extends ElementoCodigo implements Definicion { Nombre nombre; RefTipo refTipo; 163 Sobre la información, su estructura y su-gestión public DefVariable(Nombre nombre, RefTipo refTipo) { this.nombre = nombre; this.refTipo = refTipo; } public RefTipo getRefTipo(){ return refTipo; } public Nombre getNombre(){ return nombre; } public String getRepTextual(){ return nombre+" : "+refTipo.getRepTextual(); } public void resuelveReferencias(GestorAmbito ambito, ErroresReferencias errores){ refTipo.resuelveReferencias(ambito,errores); } public void convertirEnEjecutable(java.io.PrintWriter salida, GestorIdentificadores gestor){ this.setCodigo(gestor); salida.println("class "+this.getNombreEjecucion()+" extends ItemDeInformacion {"); salida.println(" public "+this.getNombreEjecucion()+"(){}"); salida.println("}"); } } package tfc; import javax.swing.tree.*; public class DefVariableTreeNode extends TreeNode { DefVariable defVariable; NombreTreeNode nombre; RefTipoTreeNode refTipo; public ElementoCodigoInterfaz getElementoCodigo(){ return defVariable; } public DefVariableTreeNode(DefVariable defVariable) { this.setModeloRecursivo(defVariable); } public DefVariableTreeNode() { this.setCabecera("Definicion de Variable -> "); } public void setModelo(Object defVariable){ this.defVariable = (DefVariable) defVariable; } public int getIndexOfChild(Object child){ if (child == nombre){ return 0; } else { return 1; } } public void valueForPathChanged(TreePath path, Object newValue){ // De momento no se permite la edición. } public boolean isLeaf(){ 164 Sobre la información, su estructura y su-gestión return false; } public int getChildCount(){ return 2; } public Object getChild(int index){ switch (index) { case 0: return nombre; case 1: return refTipo; default: return null; } } public void setModeloRecursivo(Object obj){ this.setModelo(obj); nombre = new NombreTreeNode(defVariable.getNombre()); refTipo = new RefTipoTreeNode(defVariable.getRefTipo()); } public String toString(){ return defVariable.toString(); } } package tfc; public class Ejecucion { public Ejecucion() { } } package tfc; import java.io.PrintWriter; public interface EjecutableEjecucionI { public void ejecuta(InformacionAccesible i, PrintWriter salida); public void fin(); } package tfc; public interface EjecutableTreeNodeI extends TreeNodeI { public void setCabecera(String cabecera); } package tfc; import java.util.Vector; import javax.swing.event.*; public abstract class ElementoCodigo implements ElementoCodigoInterfaz { GestorAmbito ambitoSuperior; int codigo; public String getRepTextual(){ return super.toString(); } public String toString(){ return this.getRepTextual(); } public void setAmbitoSuperior(GestorAmbito ambitoSuperior){ this.ambitoSuperior = ambitoSuperior; 165 Sobre la información, su estructura y su-gestión } public void setCodigo(GestorIdentificadores gestor){ this.codigo = gestor.getCodigo(); } public String getNombreEjecucion(){ StringBuffer nombreCompleto = new StringBuffer(this.getClass().getName()); int tamañoPaquete = this.getClass().getpackage().getName().length(); return nombreCompleto.substring(tamañoPaquete+1,nombreCompleto.length())+"Ejecucion"+this. codigo; } public int getCodigo(){ return codigo; } } package tfc; import java.io.PrintWriter; public interface ElementoCodigoEjecucion extends ElementoCodigoInterfaz { public gestor); public public public } package tfc; void convertirEnEjecutable(PrintWriter salida, GestorIdentificadores String getNombreEjecucion(); void setCodigo(GestorIdentificadores gestor); int getCodigo(); import java.io.PrintWriter; public interface ElementoCodigoInterfaz { public void resuelveReferencias(GestorAmbito ambito, ErroresReferencias errores); } package tfc; public class Entero extends Ordinal { static Entero yo = null; public static Entero getInstance() { if (yo == null) { yo = new Entero(); } return yo; } private Entero(){ this.setNombre(new Nombre("integer")); this.representacion = "Números enteros (integer)"; } public Valor getValorAleatorio(){ return new ValorSimple("0",this); } } package tfc; public abstract class Enumerado extends Ordinal { 166 Sobre la información, su estructura y su-gestión } package tfc; public class ErrorCompilacionException extends Exception { public ErrorCompilacionException(String s) { super(s); } } package tfc; public class ErroresCompTipos { public ErroresCompTipos() { } } package tfc; import java.util.Vector; public class ErroresReferencias { protected Vector referencias = new Vector(); public ErroresReferencias() { } public void addError(Referencia ref){ referencias.add(ref); } public boolean hayErrores(){ return (referencias.size() != 0); } public String toString(){ String resultado = ""; for(int i=0; i<referencias.size(); i++){ Referencia ref = (Referencia)referencias.get(i); resultado += "No encontrado un "+ref.getClass().getName() +" de nombre "+ref.getNombre()+'\n'; } return resultado; } } package tfc; import java.io.PrintWriter; public interface Evaluable extends ElementoCodigoEjecucion { public DefTipo getDefTipo(); public String getRepTextual(); } package tfc; import java.io.PrintWriter; public interface EvaluableEjecucionI { public Valor evaluate(InformacionAccesible i, PrintWriter salida); } package tfc; public abstract class EvaluableInstancia { 167 Sobre la información, su estructura y su-gestión public static Evaluable getEvaluable(Nombre nombre, GestorAmbito ambito) throws DefinicionNoEncontradaException { Evaluable e; try { DefConstante d = ambito.getDefConstante(nombre,true); e = new RefConstante(d); } catch (DefinicionNoEncontradaException e1) { try { DefVariable v = ambito.getDefVariable(nombre,true); e = new RefVariable(v); } catch (DefinicionNoEncontradaException e2){ try { DefSubprograma s = ambito.getDefSubprograma(nombre,new ParametrosActuales(),true); e = new RefFuncion((DefFuncion)s); } catch(ClassCastException e4){ throw new DefinicionNoEncontradaException(nombre,"evaluable"); } } } return e; } } package tfc; public class EvaluableTreeNode { public EvaluableTreeNode() { } } package tfc; public interface EvaluableTreeNodeI extends TreeNodeI { public void setCabecera(String cabecera); } package tfc; import import import import java.awt.*; java.awt.event.*; javax.swing.*; java.io.*; public class Frame2 extends JFrame { DefPrograma defPrograma; JPanel contentPane; BorderLayout borderLayout1 = new BorderLayout(); JToolBar jToolBar1 = new JToolBar(); JSplitPane jSplitPane1 = new JSplitPane(); JButton jButton3 = new JButton(); Pascal pascal; // Analizador Sintáctico. JScrollPane jScrollPane4 = new JScrollPane(); JSplitPane jSplitPane2 = new JSplitPane(); JScrollPane jScrollPane3 = new JScrollPane(); GestorCompilacion gestorCompilacion = new GestorCompilacion(); boolean compilado = false; boolean resuelto = false; JSplitPane jSplitPane3 = new JSplitPane(); JScrollPane jScrollPane1 = new JScrollPane(); JScrollPane jScrollPane2 = new JScrollPane(); JButton jButton5 = new JButton(); private JTextArea jTextArea3 = new JTextArea(); private JTextArea jTextArea1 = new JTextArea(); private JTextArea jTextArea2 = new JTextArea(); private JTree jTree1 = new JTree(); 168 Sobre la información, su estructura y su-gestión //Construct the frame public Frame2() { enableEvents(AWTEvent.WINDOW_EVENT_MASK); try { jbInit(); } catch(Exception e) { e.printStackTrace(); } } //Component initialization private void jbInit() throws Exception { //setIconImage(Toolkit.getDefaultToolkit().createImage(Frame2.class.getResou rce("[Your Icon]"))); contentPane = (JPanel) this.getContentPane(); contentPane.setLayout(borderLayout1); this.setSize(new Dimension(556, 486)); this.setTitle("Frame Title"); jButton3.setText("Compilación"); jButton3.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent e) { jButton3_actionPerformed(e); } }); jSplitPane2.setOrientation(JSplitPane.VERTICAL_SPLIT); jSplitPane3.setOrientation(JSplitPane.VERTICAL_SPLIT); jButton5.setText("Ejecucion"); jButton5.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent e) { jButton5_actionPerformed(e); } }); jTextArea1.addInputMethodListener(new java.awt.event.InputMethodListener() { public void inputMethodTextChanged(InputMethodEvent e) { jTextArea1_inputMethodTextChanged(e); } public void caretPositionChanged(InputMethodEvent e) { } }); jTextArea1.setToolTipText(""); jTextArea1.setText("program hola; \nconst\nkk=5;\nvar\n i:integer;\n\n\tprocedure hola(c:integer);\n" + " begin\n c:=456;\n\tend;\nbegin \n i:=5; hola(i);\nend."); jTree1.setModel(null); contentPane.add(jToolBar1, BorderLayout.SOUTH); jToolBar1.add(jButton3, null); jToolBar1.add(jButton5, null); contentPane.add(jSplitPane1, BorderLayout.CENTER); jSplitPane1.add(jSplitPane2, JSplitPane.LEFT); jSplitPane2.add(jScrollPane4, JSplitPane.RIGHT); jScrollPane4.getViewport().add(jTextArea2, null); jSplitPane2.add(jScrollPane3, JSplitPane.LEFT); jScrollPane3.getViewport().add(jTree1, null); jSplitPane1.add(jSplitPane3, JSplitPane.RIGHT); jSplitPane3.add(jScrollPane1, JSplitPane.BOTTOM); jScrollPane1.getViewport().add(jTextArea3, null); jSplitPane3.add(jScrollPane2, JSplitPane.TOP); jScrollPane2.getViewport().add(jTextArea1, null); jSplitPane1.setDividerLocation(250); jSplitPane2.setDividerLocation(150); jSplitPane3.setDividerLocation(200); } //Overridden so we can exit when window is closed protected void processWindowEvent(WindowEvent e) { super.processWindowEvent(e); 169 Sobre la información, su estructura y su-gestión if (e.getID() == WindowEvent.WINDOW_CLOSING) { System.exit(0); } } void jButton3_actionPerformed(ActionEvent e) { try { gestorCompilacion.analizaSintácticamente(jTextArea1.getText()); defPrograma = gestorCompilacion.getDefPrograma(); jTree1.setModel( new DefProgramaTreeModel(defPrograma)); ErroresReferencias errores = gestorCompilacion.resuelveReferencias(); if (errores.hayErrores()) { jTextArea2.setText( errores.toString() ); jButton5.setEnabled(false); } else { jTextArea2.setText("Programa correcto"); jButton5.setEnabled(true); } } catch (ParseException p){ jTextArea2.append(p.getMessage()); jButton5.setEnabled(false); } catch (TokenMgrError p){ jTextArea2.append(p.getMessage()); jButton5.setEnabled(false); } } void jTextArea1_inputMethodTextChanged(InputMethodEvent e) { if (compilado) { jTextArea2.setText(""); jTree1.setEnabled(false); jButton5.setEnabled(false); } } void jTextArea1_caretPositionChanged(InputMethodEvent e) { } void jButton5_actionPerformed(ActionEvent e) { // Aqui hay que generar el fichero y compilarle. StringWriter stringWriter = new StringWriter(); PrintWriter flujoSalida = new PrintWriter(stringWriter); defPrograma.convertirEnEjecutable(flujoSalida, new GestorIdentificadores()); CompiladorJava comp = new CompiladorJava("javac"); try { Class clase = comp.compila( new String(stringWriter.getBuffer()), "DefProgramaEjecucion0" ); Object programa = clase.newInstance(); StringWriter stringWriter2 = new StringWriter(); PrintWriter flujoSalida2 = new PrintWriter(stringWriter2); ((DefProgramaEjecucion)programa).ejecuta(null, flujoSalida2); jTextArea3.setText(stringWriter2.getBuffer().toString()); } catch (ErrorCompilacionException e2){ e2.printStackTrace(); } catch (IllegalAccessException e2){ e2.printStackTrace(); } catch (InstantiationException e2){ 170 Sobre la información, su estructura y su-gestión e2.printStackTrace(); } } } package tfc; import java.util.Vector; import java.util.Iterator; import javax.swing.AbstractListModel; public class GestorAmbito { GestorAmbito superior; SecuenciaDefTipo tipos; SecuenciaDefVariable variables; SecuenciaDefConstante constantes; SecuenciaDefSubprograma subprogramas; SecuenciaDefParametro parametros; public GestorAmbito(SecuenciaDefTipo tipos, SecuenciaDefVariable variables, SecuenciaDefConstante constantes, SecuenciaDefSubprograma subprogramas, SecuenciaDefParametro parametros, GestorAmbito superior){ this.setDefiniciones(tipos,variables,constantes,subprogramas,parametros,supe rior); } protected GestorAmbito(){ } protected void setDefiniciones(SecuenciaDefTipo tipos, SecuenciaDefVariable variables, SecuenciaDefConstante constantes, SecuenciaDefSubprograma subprogramas, SecuenciaDefParametro parametros, GestorAmbito superior){ this.tipos = tipos; this.variables = variables; this.constantes = constantes; this.subprogramas = subprogramas; this.superior = superior; this.parametros = parametros; } public DefTipo getDefTipo(Nombre nombre, boolean recursivo) throws DefinicionNoEncontradaException { DefTipo t = (DefTipo)this.getDefinicion(nombre.toString(),tipos); if (t == null) { if (!recursivo || superior == null){ throw new DefinicionNoEncontradaException(nombre,"tipo"); } else { return superior.getDefTipo(nombre,true); } } else { return t; } } public DefVariable getDefVariable(Nombre nombre, boolean recursivo) throws DefinicionNoEncontradaException { DefVariable t = (DefVariable)this.getDefinicion(nombre.toString(),variables); if (t == null) { 171 Sobre la información, su estructura y su-gestión t = (DefVariable)this.getDefinicion(nombre.toString(),parametros); if (t == null) { if (!recursivo || superior == null){ throw new DefinicionNoEncontradaException(nombre,"variable o parámetro"); } else { return superior.getDefVariable(nombre,true); } } else { return t; } } else { return t; } } public DefConstante getDefConstante(Nombre nombre, boolean recursivo) throws DefinicionNoEncontradaException { DefConstante t = (DefConstante)this.getDefinicion(nombre.toString(),constantes); if (t == null) { if (!recursivo || superior == null){ throw new DefinicionNoEncontradaException(nombre,"constante"); } else { return superior.getDefConstante(nombre,true); } } else { return t; } } public DefSubprograma getDefSubprograma(Nombre nombre, ParametrosActuales p, boolean recursivo) throws DefinicionNoEncontradaException { DefSubprograma t; posicion = 0; // Esto es una ñapa y una "patá" a la programacion estructurada y por tanto a la // programacion orientada a objetos, pero es para hacer una prueba, hay que retocarlo. do { t = (DefSubprograma)this.getDefinicion(nombre.toString(),subprogramas,false); if (t == null) { if (!recursivo || superior == null){ throw new DefinicionNoEncontradaException(nombre,"subprograma"); } else { return superior.getDefSubprograma(nombre,p,true); } } } while ( !t.compatible(p) ); return t; } private Definicion getDefinicion(String nombre, Secuencia definiciones) { return getDefinicion(nombre, definiciones, true); } 172 Sobre la información, su estructura y su-gestión private int posicion = 0; private Definicion getDefinicion(String nombre, Secuencia definiciones, boolean nuevaBusqueda) { boolean seguir = true; Definicion t = null; if (nuevaBusqueda) { posicion = 0; } while ( posicion < definiciones.getSize() && seguir ) { t = (Definicion)definiciones.getElementAt(posicion); seguir = !( nombre.equalsIgnoreCase( t.getNombre().toString() ) ) ; posicion++; } if (seguir){ return null; } else { return t; } } public String String salida salida getRepTextual(){ salida = ""; += "Subprogramas\n"; += this.subprogramas.getRepTextual()+"\n"; salida += "Variables\n"; salida += this.variables.getRepTextual()+"\n"; salida += "Constantes\n"; salida += this.constantes.getRepTextual()+"\n"; salida += "Parametros\n"; salida += this.parametros.getRepTextual()+"\n"; salida += "Tipos\n"; salida += this.tipos.getRepTextual()+"\n"; salida += "Superior\n"; salida += (this.superior == null)?"No tiene":this.superior.getRepTextual(); return salida.toString(); } } package tfc; import java.io.StringReader; public class GestorCompilacion { private boolean sintacticoCorrecto; private Pascal analisisSintactico = new Pascal(new StringReader("")); private DefPrograma defPrograma; public GestorCompilacion() { sintacticoCorrecto = false; } public DefPrograma getDefPrograma(){ return defPrograma; } 173 Sobre la información, su estructura y su-gestión public void analizaSintácticamente(String fuente) throws ParseException, TokenMgrError { analisisSintactico.ReInit( new StringReader(fuente)); defPrograma = analisisSintactico.programa(); sintacticoCorrecto = true; } public boolean isSintacticoCorrecto(){ return sintacticoCorrecto; } public ErroresReferencias resuelveReferencias() { if (sintacticoCorrecto) { return defPrograma.resuelveReferencias(); } else { return null; } } } package tfc; public class GestorIdentificadores { int numero; int nAmbito; public GestorIdentificadores() { numero = 0; nAmbito = 0; } public int getCodigo(){ numero++; return numero-1; } public int setNumInformacion(){ nAmbito++; return nAmbito; } public int getNumInformacion(){ return nAmbito; } } package tfc; import java.util.Vector; import java.io.PrintWriter; public abstract class InformacionAccesible extends Ejecucion { protected InformacionAccesible ambitoSuperior; protected Vector v = new Vector(); protected void setAmbitoSuperior(InformacionAccesible ambitoSuperior){ this.ambitoSuperior = ambitoSuperior; } public void creacion(InformacionAccesible inf, PrintWriter salida){ creacion(null,inf,salida); } public abstract void creacion(ParametrosActualesValoresEItems p, InformacionAccesible inf, PrintWriter salida); public abstract ItemDeInformacion getItem(int codigo); public void liberacion(PrintWriter salida) { for (int i=0; i<v.size(); i++){ 174 Sobre la información, su estructura y su-gestión ((ItemDeInformacion)v.get(i)).liberacion(salida); } } static int codigo; public static void setCodigo(GestorIdentificadores gestor){ codigo = gestor.getCodigo(); } public static String getNombreEjecucion(){ return "InformacionAccesible"+codigo; } public static int convertirEnEjecutable(java.io.PrintWriter salida, GestorIdentificadores gestor, SecuenciaDefConstante constantes, SecuenciaDefParametro parametros, SecuenciaDefVariable variables){ setCodigo(gestor); salida.println("class "+getNombreEjecucion()+" extends InformacionAccesible {"); salida.println(" public void creacion(ParametrosActualesValoresEItems p, InformacionAccesible ambitoSuperior, java.io.PrintWriter salida){ "); salida.println("ItemDeInformacion item;"); for(int i=0; i<constantes.getSize();i++){ salida.println("item = new "+constantes.getDefConstanteAt(i).getNombreEjecucion()+"();"); salida.println("item.creacion(salida);"); salida.println("v.add(item);"); } salida.println("ItemDeInformacionParam item2;"); for(int i=0; i<parametros.getSize();i++){ salida.println("item2 = new "+parametros.getDefParametroAt(i).getNombreEjecucion()+"();"); salida.println("item2.creacion(p,"+i+",salida);"); salida.println("v.add(item2);"); } for(int i=0; i<variables.getSize();i++){ salida.println("item = new "+variables.getDefVariableAt(i).getNombreEjecucion()+"();"); salida.println("item.creacion(salida);"); salida.println("v.add(item);"); } salida.println(" }"); salida.println(" public ItemDeInformacion getItem(int codigo){"); salida.println(" switch(codigo){ "); int j=0; for(int i=0; i<constantes.getSize();i++){ salida.println("case "+constantes.getDefConstanteAt(i).getCodigo()+": return (ItemDeInformacion)v.get("+j+");"); j++; } for(int i=0; i<parametros.getSize();i++){ salida.println("case "+parametros.getDefParametroAt(i).getCodigo()+": return (ItemDeInformacion)v.get("+j+");"); j++; } for(int i=0; i<variables.getSize();i++){ salida.println("case "+variables.getDefVariableAt(i).getCodigo()+": return (ItemDeInformacion)v.get("+j+");"); j++; } 175 Sobre la información, su estructura y su-gestión salida.println(" default: return ambitoSuperior.getItem(codigo);"); salida.println(" }"); salida.println(" }"); salida.println("}"); return codigo; } } package tfc; import java.io.PrintWriter; public class InformacionAccesiblePredefinida extends InformacionAccesible { public void creacion(ParametrosActualesValoresEItems p, InformacionAccesible ambitoSuperior, PrintWriter salida){ ItemDeInformacion maxint = new ItemDeInformacion(); maxint.setValor(Entero.getInstance().getValor("1000000000"),salida); v.add(maxint); } public ItemDeInformacion getItem(int codigo){ return (ItemDeInformacion)v.get(0); } } package tfc; public abstract class InformacionAccesibleSubprograma extends InformacionAccesible { public InformacionAccesibleSubprograma() { } public void creacion(InformacionAccesible ambitoSuperior){ throw new Error("Que cojones haces ejecutando este método"); } public abstract void creacion(ParametrosActualesValoresEItems p, InformacionAccesible ambitoSuperior); } package tfc; import javax.swing.DefaultListModel; public class InstruccionCompuesta extends ElementoCodigo implements Sentencia { protected SecuenciaEjecutable instrucciones; public InstruccionCompuesta(){ this.instrucciones = new SecuenciaEjecutable(); } public SecuenciaEjecutable getInstrucciones(){ return instrucciones; } public String getRepTextual(){ String resultado = ""; resultado += "BEGIN"+'\n'; for(int i=0; i < instrucciones.getSize(); i++){ resultado += (instrucciones.getEjecutableAt(i)).getRepTextual() + ";\n"; } resultado += "END"; return resultado; } 176 Sobre la información, su estructura y su-gestión public void resuelveReferencias(GestorAmbito ambitoSuperior, ErroresReferencias errores){ instrucciones.resuelveReferencias(ambitoSuperior,errores); } public void convertirEnEjecutable(java.io.PrintWriter salida, GestorIdentificadores gestor){ for(int i=0; i<this.instrucciones.getSize();i++){ ((Sentencia)this.instrucciones.get(i)).convertirEnEjecutable(salida,gestor); } this.setCodigo(gestor); salida.println("//----------------------------------------------------------------------------------"); salida.println("class "+this.getNombreEjecucion()+" extends SecuenciaEjecutableEjecucion { "); public "+this.getNombreEjecucion()+"(){ salida.println(" "); salida.println(" this.setNElementos("+this.instrucciones.getSize()+"); "); } salida.println(" "); salida.println(" public void ejecutaYFin(int i,InformacionAccesible inf, java.io.PrintWriter salida) { "); salida.println(" EjecutableEjecucionI e; "); salida.println(" switch(i){ "); for(int i=0; i<this.instrucciones.getSize();i++){ case "+i+": { salida.println(" "); salida.println(" e = new "+((Sentencia)this.instrucciones.get(i)).getNombreEjecucion()+"(); "); salida.println(" e.ejecuta(inf,salida); "); salida.println(" e.fin(); "); } break; salida.println(" "); } salida.println(" salida.println(" }"); salida.println(" }"); }"); } } package tfc; import javax.swing.tree.*; import javax.swing.DefaultListModel; public class InstruccionCompuestaTreeNode extends TreeNode { InstruccionCompuesta instruccionCompuesta; DefaultListModel instrucciones = new DefaultListModel(); public ElementoCodigoInterfaz getElementoCodigo(){ return instruccionCompuesta; 177 Sobre la información, su estructura y su-gestión } public InstruccionCompuestaTreeNode(InstruccionCompuesta instruccionCompuesta) { this.setModeloRecursivo(instruccionCompuesta); } public InstruccionCompuestaTreeNode() { } public boolean isLeaf() { return false; } public Object getChild(int index) { return instrucciones.get(index); } public int getIndexOfChild(Object child) { return instrucciones.indexOf(child); } public int getChildCount() { return instrucciones.getSize(); } public void valueForPathChanged(TreePath path, Object newValue) { } public void setModelo(Object obj) { instruccionCompuesta = (InstruccionCompuesta) obj; } public void setModeloRecursivo(Object obj){ this.setModelo(obj); SecuenciaEjecutable aux = instruccionCompuesta.getInstrucciones(); for(int i=0; i<aux.getSize();i++){ instrucciones.addElement( TreeNode.getTreeNode(aux.get(i)) ); } } public String toString(){ return cabecera; } } package tfc; import java.io.PrintWriter; public abstract class InstruccionEjecucion extends Ejecucion implements EjecutableEjecucionI { public abstract void ejecuta(InformacionAccesible i, PrintWriter salida); public void fin(){ } } package tfc; import javax.swing.tree.TreePath; public abstract class InstruccionTreeNode extends TreeNode implements EjecutableTreeNodeI { } package tfc; public class InstruccionVacia extends ElementoCodigo implements Sentencia { public InstruccionVacia() { 178 Sobre la información, su estructura y su-gestión } public String getRepTextual(){ return ""; } public void resuelveReferencias(GestorAmbito parm1, ErroresReferencias parm2) { // No hay referencias en este elemento. } public void convertirEnEjecutable(java.io.PrintWriter salida, GestorIdentificadores gestor){ this.setCodigo(gestor); salida.println("class "+this.getNombreEjecucion()+" implements EjecutableEjecucionI {"); salida.println("public void ejecuta(InformacionAccesible i, java.io.PrintWriter salida){}"); salida.println("public void fin(){}"); salida.println("}"); } } package tfc; import javax.swing.tree.TreePath; public class InstruccionVaciaTreeNode extends InstruccionTreeNode { InstruccionVacia instruccion; public InstruccionVaciaTreeNode() { } public boolean isLeaf() { return true; } public Object getChild(int parm1) { return null; } public void setModeloRecursivo(Object obj) { this.instruccion = (InstruccionVacia) obj; this.setCabecera("Instruccion Vacia"); } public int getIndexOfChild(Object parm1) { return -1; } public int getChildCount() { return 0; } public void valueForPathChanged(TreePath parm1, Object parm2) { } public ElementoCodigoInterfaz getElementoCodigo() { return instruccion; } public void setModelo(Object obj) { this.instruccion = (InstruccionVacia) obj; } } package tfc; import java.io.PrintWriter; public class ItemDeInformacion extends Ejecucion implements EvaluableEjecucionI { Valor valor; public void creacion(PrintWriter salida){ salida.println("Reservada memoria variable"); valor = Entero.getInstance().getValorAleatorio(); 179 Sobre la información, su estructura y su-gestión } public Valor evaluate(InformacionAccesible i, PrintWriter salida){ salida.println("El valor es "+valor.getRepTextual()); return valor; } public void setValor(Valor valor, PrintWriter salida){ if (this.valor == null){ salida.println("No tenía valor y ahora tiene el valor "+valor.getRepTextual()); } else { salida.println("Tenía el valor "+this.valor.getRepTextual()+" y ahora tiene el valor "+valor.getRepTextual()); } this.valor = valor; } public void liberacion(PrintWriter salida){ salida.println("Liberada memoria variable"); } } package tfc; public class ItemDeInformacionParam extends ItemDeInformacion { public ItemDeInformacionParam() { } public void creacion(ParametrosActualesValoresEItems p,int i,java.io.PrintWriter salida){ Valor v = p.getValor(i); salida.println("Reservada memoria variable con el valor"+v.getRepTextual()); valor = v; } } package tfc; public class Nombre implements ElementoCodigoInterfaz { String nombre; public Nombre(String nombre) { this.nombre = nombre; } public void resuelveReferencias(GestorAmbito ambito, ErroresReferencias errores) { } public String toString(){ return nombre; } public boolean equalsIgnoreCase( Nombre n){ return nombre.equalsIgnoreCase(n.toString()); } } package tfc; import javax.swing.tree.*; // La clase String debería de ser la clase nombre, ya lo cambiaremos. public class NombreTreeNode extends TreeNode { Nombre nombre; public ElementoCodigoInterfaz getElementoCodigo(){ 180 Sobre la información, su estructura y su-gestión return nombre; } public NombreTreeNode() { setCabecera("Nombre -> "); } public NombreTreeNode(Nombre nombre) { this.setModelo(nombre); setCabecera("Nombre -> "); } public boolean isLeaf() { return true; } public Object getChild(int index) { return null; } public int getIndexOfChild(Object child) { return -1; } public int getChildCount() { return 0; } public void valueForPathChanged(TreePath path, Object newValue) { } public void setModelo(Object obj) { this.nombre = (Nombre) obj; } public void setModeloRecursivo(Object obj){ this.nombre = (Nombre) obj; } } package tfc; public class NoRefVariableException extends Exception { public NoRefVariableException() { } } package tfc; import java.util.Vector; import java.util.Iterator; public abstract class Ordinal extends TipoSimple { Vector valores = new Vector(100,10); public int getNumeroOrden(ValorSimple v){ return valores.indexOf(v); } } package tfc; import java.util.*; public class ParametrosActuales extends ElementoCodigo { Vector refVariables = new Vector(); Vector generadoresValor = new Vector(); public ParametrosActuales() { } public void addRefVariable(RefVariable rV){ refVariables.add(rV); 181 Sobre la información, su estructura y su-gestión generadoresValor.add(null); } public void addEvaluable(Evaluable gV){ refVariables.add(null); generadoresValor.add(gV); } public String getRepTextual(){ String resultado = ""; if ( refVariables.size() != 0){ for(int i=0; i < refVariables.size() - 1; i++){ if (refVariables.get(i) != null){ resultado += ((ElementoCodigo)refVariables.get(i)).getRepTextual()+","; } else { resultado += ((ElementoCodigo)generadoresValor.get(i)).getRepTextual()+","; } } if (refVariables.lastElement() != null){ resultado += ((ElementoCodigo)refVariables.lastElement()).getRepTextual(); } else { resultado += ((ElementoCodigo)generadoresValor.lastElement()).getRepTextual(); } } return resultado; } public boolean compatible(SecuenciaDefParametro paramFormales){ if (paramFormales.size() != refVariables.size()){ return false; } else { boolean seguir = true; int i=0; while( i < refVariables.size() && seguir){ seguir = !( (!((DefParametro)paramFormales.get(i)).pasoPorValor()) && (refVariables.get(i) == null )); Evaluable parametroActual = (Evaluable)((refVariables.get(i) == null)? generadoresValor.get(i) : refVariables.get(i)); seguir = seguir && ( ((DefParametro)paramFormales.get(i)).getRefTipo().getDefTipo() == parametroActual.getDefTipo() ); i++; } return seguir; } } RefVariable getRefVariable(int pos) throws NoRefVariableException { if (refVariables.get(pos) != null ){ return (RefVariable)refVariables.get(pos); } else { throw new NoRefVariableException(); } } Evaluable getGeneradorValor(int pos) { if (refVariables.get(pos) != null ){ return (Evaluable)refVariables.get(pos); } else { return (Evaluable)generadoresValor.get(pos); } } 182 Sobre la información, su estructura y su-gestión int getSize(){ return refVariables.size(); } public void resuelveReferencias(GestorAmbito ambito, ErroresReferencias errores){ for(int i=0; i<refVariables.size();i++){ RefVariable refVariable = (RefVariable)refVariables.get(i); if (refVariable != null) { refVariable.resuelveReferencias(ambito,errores); } } for(int i=0; i<generadoresValor.size();i++){ Evaluable evaluable = (Evaluable)generadoresValor.get(i); if (evaluable != null) { evaluable.resuelveReferencias(ambito,errores); } } } } package tfc; // De momento esta clase va a albergar simplemete valores para aquellos parámetros que sean por valor // e ItemDeInformacion a aquellos parámetros por referencia. public abstract class ParametrosActualesEjecucion extends Ejecucion { public ParametrosActualesEjecucion() { } public abstract ParametrosActualesValoresEItems getValoresEItems(InformacionAccesible i); public void fin(){ } } package tfc; import javax.swing.DefaultListModel; import javax.swing.tree.TreePath; public class ParametrosActualesTreeNode extends TreeNode { ParametrosActuales parametrosActuales; DefaultListModel parametros = new DefaultListModel(); public ElementoCodigoInterfaz getElementoCodigo(){ return parametrosActuales; } public ParametrosActualesTreeNode() { setCabecera("Parametros actuales -> "); } public void setModeloRecursivo(Object obj) { this.setModelo(obj); for(int i=0; i<parametrosActuales.getSize();i++){ parametros.addElement( TreeNode.getTreeNode(parametrosActuales.getGeneradorValor(i)) ); } } public boolean isLeaf() { return false; } public Object getChild(int index) { return parametros.get(index); 183 Sobre la información, su estructura y su-gestión } public int getIndexOfChild(Object child) { return parametros.indexOf(child); } public int getChildCount() { return parametros.getSize(); } public void valueForPathChanged(TreePath path, Object newValue) { } public void setModelo(Object obj) { this.parametrosActuales = (ParametrosActuales)obj; } } package tfc; import java.util.Vector; public class ParametrosActualesValoresEItems extends Ejecucion { Vector v = new Vector(); public ParametrosActualesValoresEItems() { } public void addValor(Valor valor){ v.add(valor); } public void addItemDeInformacion(ItemDeInformacion item){ v.add(item); } public Valor getValor(int i){ try { return (Valor)v.get(i); } catch(ClassCastException e){ return null; } } public ItemDeInformacion getItemDeInformacion(int i){ try { return (ItemDeInformacion)v.get(i); } catch(ClassCastException e){ return null; } } } package tfc; import javax.swing.DefaultListModel; public class Predefinidos extends GestorAmbito { public Predefinidos() { setDefiniciones(tipos(), variables(), constantes(), subprogramas(), new SecuenciaDefParametro(), null); } private SecuenciaDefTipo tipos(){ SecuenciaDefTipo tipos; tipos = new SecuenciaDefTipo(); 184 Sobre la información, su estructura y su-gestión tipos.addElement( tipos.addElement( tipos.addElement( tipos.addElement( return tipos; Entero.getInstance() ); Real.getInstance() ); Booleano.getInstance() ); Caracter.getInstance() ); } private SecuenciaDefVariable variables(){ return new SecuenciaDefVariable(); } private SecuenciaDefConstante constantes(){ SecuenciaDefConstante constantes; constantes = new SecuenciaDefConstante(); DefConstante c; c = new DefConstante( new Nombre("true") , Booleano.getInstance().getValor("true") ); constantes.addElement( c ); c = new DefConstante( new Nombre("false") , Booleano.getInstance().getValor("false") ); constantes.addElement( c ); return constantes; } private SecuenciaDefSubprograma subprogramas(){ SecuenciaDefSubprograma subprogramas = new SecuenciaDefSubprograma(); subprogramas.addElement( subprogramas.addElement( subprogramas.addElement( subprogramas.addElement( subprogramas.addElement( subprogramas.addElement( subprogramas.addElement( subprogramas.addElement( subprogramas.addElement( subprogramas.addElement( return subprogramas; new new new new new new new new new new DefFuncionOperadorSuma() ); DefFuncionOperadorResta() ); DefFuncionOperadorNegacion() ); DefFuncionOperadorMultiplicacion() ); DefFuncionOperadorDivision() ); DefFuncionOperadorMod() ); DefFuncionOperadorAnd() ); DefFuncionOperadorOr() ); DefFuncionOperadorNot() ); DefFuncionOperadorIgualdad() ); } } package tfc; public class Real extends TipoSimple { static Real yo = null; public static Real getInstance() { if (yo == null) { yo = new Real(); } return yo; } private Real(){ this.setNombre(new Nombre("real")); this.representacion = "Números reales (double o float)"; } } package tfc; public class RefConstante extends Referencia implements Evaluable { DefConstante c; 185 Sobre la información, su estructura y su-gestión public RefConstante(DefConstante c) { this.nombre = c.getNombre(); // Revisar este constructor. this.c = c; } public RefConstante(Nombre nombre, GestorAmbito ambito) throws DefinicionNoEncontradaException { this.nombre = nombre; this.c = ambito.getDefConstante(nombre, true); } public Definicion getDefinicion() { return c; } public DefConstante getDefConstante(){ return c; } public DefTipo getDefTipo(){ return c.getValor().getDefTipo(); } public Valor getValor(){ return c.getValor(); } public void resuelveReferencias(GestorAmbito ambito, ErroresReferencias errores){ try { ambito.getDefConstante(this.getNombre(),true); } catch (DefinicionNoEncontradaException e){ errores.addError(this); } } public void convertirEnEjecutable(java.io.PrintWriter salida, GestorIdentificadores gestor){ this.codigo = gestor.getCodigo(); salida.println("//------------------------------------------------------------------------"); salida.println("class "+this.getNombreEjecucion()+" extends RefConstanteEjecucion {"); salida.println(" public ItemDeInformacion getItemDeInformacion(InformacionAccesible i){"); salida.println(" return i.getItem("+this.getDefConstante().getCodigo()+");"); salida.println(" }"); salida.println("}"); } } package tfc; import java.io.PrintWriter; public abstract class RefConstanteEjecucion implements EvaluableEjecucionI { public RefConstanteEjecucion() { } public Valor evaluate(InformacionAccesible i, PrintWriter salida) { salida.println("Inicio de evaluación de la Constante"); Valor valor = this.getItemDeInformacion(i).evaluate(i,salida); salida.println("Fin de la evaluacion de la Constante"); return valor; } 186 Sobre la información, su estructura y su-gestión public abstract ItemDeInformacion getItemDeInformacion(InformacionAccesible i); } package tfc; import javax.swing.tree.*; public class RefConstanteTreeNode extends ReferenciaTreeNode implements EvaluableTreeNodeI { RefConstante refConstante; public ElementoCodigoInterfaz getElementoCodigo(){ return refConstante; } public RefConstanteTreeNode(RefConstante refConstante) { this.setModeloRecursivo(refConstante); } public RefConstanteTreeNode() { setCabecera("Referencia a Constante -> "); } public void setModelo(Object obj) { this.refConstante = (RefConstante) obj; } public void setModeloRecursivo(Object obj){ this.refConstante = (RefConstante) obj; } public String toString(){ return cabecera + refConstante.getRepTextual() + this.textoEnlazado(); } public Referencia getReferencia(){ return refConstante; } } package tfc; public abstract class Referencia extends ElementoCodigo { protected Nombre nombre; boolean enlazado = false; // Indica si se ha resuelto la referencia o no. public String getRepTextual(){ return nombre.toString(); } public Nombre getNombre(){ return nombre; } public Object getChild(int i){ return null; } public abstract Definicion getDefinicion(); public boolean isEnlazado(){ return enlazado; } } package tfc; import javax.swing.tree.TreePath; public abstract class ReferenciaTreeNode extends TreeNode { public ReferenciaTreeNode() { 187 Sobre la información, su estructura y su-gestión } public boolean isLeaf() { return true; } public int getChildCount(){ return 0; } public Object getChild(int i){ return null; } public void valueForPathChanged(TreePath path, Object newValue) { } public int getIndexOfChild(Object o){ return -1; } public String textoEnlazado(){ String salida = ""; if (this.getReferencia().isEnlazado()) { salida = " E"; } else { salida = " NE"; } return salida; } public abstract Referencia getReferencia(); } package tfc; public class RefFuncion extends RefSubprograma implements Evaluable { protected DefFuncion defFuncion = null; public RefFuncion(Nombre nombre) { super(nombre); } public RefFuncion(DefFuncion defFuncion){ super(defFuncion.getNombre()); this.defFuncion = defFuncion; } public DefSubprograma getDefSubprograma() { return defFuncion; } public Definicion getDefinicion() { return defFuncion; } public DefFuncion getDefFuncion(){ return defFuncion; } public DefTipo getDefTipo(){ return defFuncion.getDefTipo(); } public void resuelveReferencias(GestorAmbito ambito, ErroresReferencias errores){ if (this.getDefinicion() == null){ try { paramActuales.resuelveReferencias(ambito,errores); 188 Sobre la información, su estructura y su-gestión defFuncion = (DefFuncion)ambito.getDefSubprograma(this.getNombre(),paramActuales,true); enlazado = true; } catch (DefinicionNoEncontradaException e){ errores.addError(this); } } } public void convertirEnEjecutable(java.io.PrintWriter salida, GestorIdentificadores gestor){ // POR HACER } } package tfc; public abstract class RefFuncionEjecucion extends RefSubprogramaEjecucion implements EvaluableEjecucionI { Valor salidaFuncion; public RefFuncionEjecucion() { } public Valor getValor(){ return salidaFuncion; } } package tfc; public class RefFuncionOperador extends RefFuncion { protected DefFuncionOperador defFuncionOperador; public RefFuncionOperador(Nombre nombre) { super(nombre); } public DefSubprograma getDefSubprograma() { return defFuncionOperador; } public Definicion getDefinicion() { return defFuncionOperador; } public DefFuncion getDefFuncion(){ return defFuncionOperador; } public DefFuncionOperador getDefFuncionOperador(){ return defFuncionOperador; } public String getRepTextual(){ if (paramActuales.getSize() == 1){ return "( "+this.getNombre()+" " + paramActuales.getGeneradorValor(0).toString()+" )"; } else { return "( "+paramActuales.getGeneradorValor(0).toString() +" "+getNombre()+" "+ paramActuales.getGeneradorValor(1).toString()+" )"; } } } package tfc; 189 Sobre la información, su estructura y su-gestión public class RefFuncionOperadorTreeNode extends RefFuncionTreeNode { public RefFuncionOperadorTreeNode() { setCabecera("Funcion con operador -> "); } } package tfc; public class RefFuncionTreeNode extends RefSubprogramaTreeNode implements EvaluableTreeNodeI { RefFuncion refFuncion; public void setModelo(Object obj) { this.refFuncion = (RefFuncion)obj; } public RefSubprograma getRefSubprograma() { return refFuncion; } } package tfc; public class RefProcedimiento extends RefSubprograma implements Sentencia { protected DefProcedimiento defProcedimiento; public RefProcedimiento(Nombre nombre) { super(nombre); } public DefProcedimiento getDefProcedimiento(){ return defProcedimiento; } public DefSubprograma getDefSubprograma() { return defProcedimiento; } public Definicion getDefinicion() { return defProcedimiento; } public void resuelveReferencias(GestorAmbito ambito, ErroresReferencias errores){ if (this.getDefinicion() == null){ try { paramActuales.resuelveReferencias(ambito,errores); defProcedimiento = (DefProcedimiento)ambito.getDefSubprograma(this.getNombre(),paramActuales,true); enlazado = true; } catch (DefinicionNoEncontradaException e){ errores.addError(this); } } } public void convertirEnEjecutable(java.io.PrintWriter salida, GestorIdentificadores gestor){ this.setCodigo(gestor); salida.println("class "+this.getNombreEjecucion()+" extends RefSubprogramaEjecucion {"); salida.println(" public "+this.getNombreEjecucion()+"(){}"); salida.println(" public void ejecutaSubprograma(ParametrosActualesValoresEItems p,InformacionAccesible inf, java.io.PrintWriter salida){"); salida.println(" DefProcedimientoEjecucion e = new "+this.defProcedimiento.getNombreEjecucion()+"();"); salida.println(" e.ejecuta(p,inf,salida);"); 190 Sobre la información, su estructura y su-gestión salida.println(" }"); salida.println(" public ParametrosActualesValoresEItems evaluaParametros(InformacionAccesible inf,java.io.PrintWriter salida){"); salida.println(" ParametrosActualesValoresEItems p = new ParametrosActualesValoresEItems();"); salida.println(" Valor v; EvaluableEjecucionI e;"); for (int i=0; i< defProcedimiento.getParametros().size();i++){ salida.println(" e = new "+this.getParamActuales().getGeneradorValor(i).getNombreEjecucion()+"();"); salida.println(" v = e.evaluate(inf,salida);"); salida.println(" e.fin();"); salida.println(" p.addValor(v); "); } salida.println(" return p;"); salida.println(" }"); salida.println("}"); } } package tfc; public abstract class RefProcedimientoEjecucion extends RefSubprogramaEjecucion implements EjecutableEjecucionI { public RefProcedimientoEjecucion() { } } package tfc; public class RefProcedimientoTreeNode extends RefSubprogramaTreeNode { RefProcedimiento refProcedimiento; public void setModelo(Object obj) { this.refProcedimiento = (RefProcedimiento)obj; } public RefSubprograma getRefSubprograma() { return refProcedimiento; } } package tfc; import javax.swing.DefaultListModel; public abstract class RefSubprograma extends Referencia { protected ParametrosActuales paramActuales; protected RefSubprograma(Nombre nombre){ this.nombre = nombre; this.paramActuales = new ParametrosActuales(); } public abstract Definicion getDefinicion(); public abstract DefSubprograma getDefSubprograma(); public String getRepTextual(){ String resultado = ""; resultado += this.getNombre(); if (paramActuales.getSize() > 0){ resultado += "("; resultado += paramActuales.getRepTextual(); resultado += ")"; } return resultado; } 191 Sobre la información, su estructura y su-gestión public ParametrosActuales getParamActuales(){ return paramActuales; } } package tfc; public abstract class RefSubprogramaEjecucion implements EjecutableEjecucionI { public RefSubprogramaEjecucion() { } public void ejecuta(InformacionAccesible inf, java.io.PrintWriter salida){ System.out.println("Inicio ejecución llamada. Evaluacion parámetros"); ParametrosActualesValoresEItems p = this.evaluaParametros(inf,salida); this.ejecutaSubprograma(p,inf,salida); System.out.println("Fin de la llamada"); } public void fin(){ } public abstract void ejecutaSubprograma(ParametrosActualesValoresEItems p,InformacionAccesible inf, java.io.PrintWriter salida); public abstract ParametrosActualesValoresEItems evaluaParametros(InformacionAccesible inf, java.io.PrintWriter salida); } package tfc; public abstract class RefSubprogramaTreeNode extends ReferenciaTreeNode { NombreTreeNode nombre; ParametrosActualesTreeNode paramActuales; public RefSubprogramaTreeNode() { } public ElementoCodigoInterfaz getElementoCodigo(){ return getRefSubprograma(); } public Referencia getReferencia() { return getRefSubprograma(); } public boolean isLeaf() { return false; } public Object getChild(int index) { switch(index){ case 0: return nombre; default: return paramActuales; } } public int getIndexOfChild(Object child) { if (child == nombre) { return 0; } else { return 1; } } public int getChildCount() { 192 Sobre la información, su estructura y su-gestión return 2; } public void setModeloRecursivo(Object obj) { this.setModelo(obj); nombre = new NombreTreeNode( getRefSubprograma().getNombre() ); paramActuales = (ParametrosActualesTreeNode) TreeNode.getTreeNode( getRefSubprograma().getParamActuales() ); } public abstract RefSubprograma getRefSubprograma(); } package tfc; public class RefTipo extends Referencia { DefTipo t; public RefTipo(DefTipo t) { this.nombre = t.getNombre(); this.t = t; } public RefTipo(Nombre nombre, GestorAmbito ambito) throws DefinicionNoEncontradaException { this.nombre = nombre; this.t = ambito.getDefTipo(nombre, true); } public RefTipo(Nombre nombre){ this.nombre = nombre; } public Definicion getDefinicion(){ return t; } public DefTipo getDefTipo(){ return t; } public void resuelveReferencias(GestorAmbito ambito, ErroresReferencias errores){ try { t = ambito.getDefTipo(this.getNombre(),true); enlazado = true; } catch (DefinicionNoEncontradaException e){ errores.addError(this); } } } package tfc; import javax.swing.tree.*; public class RefTipoTreeNode extends ReferenciaTreeNode { RefTipo refTipo; public ElementoCodigoInterfaz getElementoCodigo(){ return refTipo; } public RefTipoTreeNode(RefTipo refTipo) { this.setModeloRecursivo(refTipo); } public RefTipoTreeNode() { } 193 Sobre la información, su estructura y su-gestión public boolean isLeaf() { return true; } public Object getChild(int index) { return null; } public int getIndexOfChild(Object child) { return -1; } public int getChildCount() { return 0; } public void valueForPathChanged(TreePath path, Object newValue) { } public void setModelo(Object obj) { refTipo = (RefTipo) obj; } public void setModeloRecursivo(Object obj){ refTipo = (RefTipo) obj; // Las hojas no necesitan ponerse el modelo recursivo } public String toString(){ return cabecera + refTipo.getNombre() + this.textoEnlazado(); } public Referencia getReferencia(){ return refTipo; } } package tfc; public class RefVariable extends Referencia implements Evaluable { DefVariable v = null; public RefVariable(Nombre nombre){ this.nombre = nombre; } public RefVariable(DefVariable defVariable){ v = defVariable; this.nombre = defVariable.getNombre(); } public Definicion getDefinicion(){ return v; } public DefVariable getDefVariable(){ return v; } public DefTipo getDefTipo(){ return v.getRefTipo().getDefTipo(); } public void resuelveReferencias(GestorAmbito ambito, ErroresReferencias errores){ try { 194 Sobre la información, su estructura y su-gestión System.out.println(ambito.getRepTextual()); v = ambito.getDefVariable(this.getNombre(),true); enlazado = true; } catch (DefinicionNoEncontradaException e){ errores.addError(this); } } public void convertirEnEjecutable(java.io.PrintWriter salida, GestorIdentificadores gestor){ this.codigo = gestor.getCodigo(); salida.println("//------------------------------------------------------------------------"); salida.println("class "+this.getNombreEjecucion()+" extends RefVariableEjecucion {"); salida.println(" public ItemDeInformacion getItemDeInformacion(InformacionAccesible i){"); salida.println(" return i.getItem("+this.getDefVariable().getCodigo()+");"); salida.println(" }"); salida.println("}"); } } package tfc; import java.io.PrintWriter; public abstract class RefVariableEjecucion implements EvaluableEjecucionI { public RefVariableEjecucion() { } public Valor evaluate(InformacionAccesible i, PrintWriter salida) { salida.println("Inicio de evaluación de la variable"); Valor valor = this.getItemDeInformacion(i).evaluate(i,salida); salida.println("Fin de la evaluacion de la variable"); return valor; } public abstract ItemDeInformacion getItemDeInformacion(InformacionAccesible i); } package tfc; import javax.swing.tree.*; public class RefVariableTreeNode extends ReferenciaTreeNode implements EvaluableTreeNodeI { RefVariable refVariable; public ElementoCodigoInterfaz getElementoCodigo(){ return refVariable; } public RefVariableTreeNode(RefVariable refVariable) { this.setModeloRecursivo(refVariable); } public RefVariableTreeNode() { setCabecera("Referencia a Variable -> "); } public void setModelo(Object obj) { this.refVariable = (RefVariable) obj; } public void setModeloRecursivo(Object obj){ 195 Sobre la información, su estructura y su-gestión this.refVariable = (RefVariable) obj; } public String toString(){ return cabecera + refVariable.getRepTextual() + this.textoEnlazado(); } public Referencia getReferencia(){ return refVariable; } } package tfc; import import import import javax.swing.AbstractListModel; java.util.Vector; java.util.Enumeration; javax.swing.event.*; public abstract class Secuencia extends AbstractListModel implements ElementoCodigoInterfaz { protected Vector delegate = new Vector(); GestorAmbito ambitoSuperior; public Secuencia() { } public void resuelveReferencias(GestorAmbito ambito ,ErroresReferencias errores){ for(int i=0; i<this.size();i++){ ElementoCodigoInterfaz elem; elem = (ElementoCodigoInterfaz)this.getElementAt(i); elem.resuelveReferencias(ambito,errores); } } public void clear() { int index1 = delegate.size()-1; delegate.removeAllElements(); if (index1 >= 0) { fireIntervalRemoved(this, 0, index1); } } public void removeRange(int fromIndex, int toIndex) { for(int i = toIndex; i >= fromIndex; i--) { delegate.removeElementAt(i); } fireIntervalRemoved(this, fromIndex, toIndex); } public Object[] toArray() { Object[] rv = new Object[delegate.size()]; delegate.copyInto(rv); return rv; } public void removeAllElements() { int index1 = delegate.size()-1; delegate.removeAllElements(); if (index1 >= 0) { fireIntervalRemoved(this, 0, index1); } } public String toString() { return delegate.toString(); } public void removeElementAt(int index) { delegate.removeElementAt(index); 196 Sobre la información, su estructura y su-gestión fireIntervalRemoved(this, index, index); } public void trimToSize() { delegate.trimToSize(); } public void ensureCapacity(int minCapacity) { delegate.ensureCapacity(minCapacity); } public void setSize(int newSize) { int oldSize = delegate.size(); delegate.setSize(newSize); if (oldSize > newSize) { fireIntervalRemoved(this, newSize, oldSize-1); } else if (oldSize < newSize) { fireIntervalAdded(this, oldSize, newSize-1); } } public int capacity() { return delegate.capacity(); } public int size() { return delegate.size(); } public boolean isEmpty() { return delegate.isEmpty(); } public Enumeration elements() { return delegate.elements(); } public int getSize() { return delegate.size(); } public Object getElementAt(int index) { return delegate.elementAt(index); } } package tfc; import java.util.Vector; import java.util.Enumeration; import javax.swing.event.*; public class SecuenciaDefConstante extends Secuencia { public SecuenciaDefConstante() { } public DefConstante getDefConstanteAt(int index) { return (DefConstante)delegate.elementAt(index); } public void copyInto(DefConstante anArray[]) { delegate.copyInto(anArray); } public boolean contains(DefConstante elem) { return delegate.contains(elem); } 197 Sobre la información, su estructura y su-gestión public int indexOf(DefConstante elem) { return delegate.indexOf(elem); } public int indexOf(DefConstante elem, int index) { return delegate.indexOf(elem, index); } public int lastIndexOf(DefConstante elem) { return delegate.lastIndexOf(elem); } public int lastIndexOf(DefConstante elem, int index) { return delegate.lastIndexOf(elem, index); } public DefConstante elementAt(int index) { return (DefConstante)delegate.elementAt(index); } public DefConstante firstElement() { return (DefConstante)delegate.firstElement(); } public DefConstante lastElement() { return (DefConstante)delegate.lastElement(); } public void setElementAt(DefConstante obj, int index) { delegate.setElementAt(obj, index); fireContentsChanged(this, index, index); } public void insertElementAt(DefConstante obj, int index) { delegate.insertElementAt(obj, index); fireIntervalAdded(this, index, index); } public void addElement(DefConstante obj) { int index = delegate.size(); delegate.addElement(obj); fireIntervalAdded(this, index, index); } public boolean removeElement(DefConstante obj) { int index = indexOf(obj); boolean rv = delegate.removeElement(obj); if (index >= 0) { fireIntervalRemoved(this, index, index); } return rv; } public DefConstante get(int index) { return (DefConstante)delegate.elementAt(index); } public DefConstante set(int index, DefConstante element) { Object rv = delegate.elementAt(index); delegate.setElementAt(element, index); fireContentsChanged(this, index, index); return (DefConstante)rv; } 198 Sobre la información, su estructura y su-gestión public void add(int index, DefConstante element) { delegate.insertElementAt(element, index); fireIntervalAdded(this, index, index); } public DefConstante remove(int index) { Object rv = delegate.elementAt(index); delegate.removeElementAt(index); fireIntervalRemoved(this, index, index); return (DefConstante)rv; } public void convertirEnEjecutable(java.io.PrintWriter salida, GestorIdentificadores gestor){ for(int i=0; i<this.getSize();i++){ ((DefConstante)this.get(i)).convertirEnEjecutable(salida,gestor); } } public String getRepTextual(){ StringBuffer salida = new StringBuffer(); for(int i=0; i<this.size(); i++){ salida.append(this.getDefConstanteAt(i).getRepTextual()+", "); } return salida.toString(); } } package tfc; public class SecuenciaDefConstanteEjecucion extends SecuenciaEjecucion { public SecuenciaDefConstanteEjecucion() { } } package tfc; import javax.swing.DefaultListModel; import javax.swing.tree.TreePath; public class SecuenciaDefConstanteTreeNode extends TreeNode { SecuenciaDefConstante secuenciaDefConstante; DefaultListModel secuenciaDefConstanteModelo = new DefaultListModel(); public ElementoCodigoInterfaz getElementoCodigo(){ return secuenciaDefConstante; } public SecuenciaDefConstanteTreeNode(SecuenciaDefConstante secuenciaDefConstante) { this.setModeloRecursivo(secuenciaDefConstante); } public SecuenciaDefConstanteTreeNode() { } public boolean isLeaf() { return false; } public Object getChild(int index) { return secuenciaDefConstanteModelo.get(index); } public int getIndexOfChild(Object child) { return secuenciaDefConstanteModelo.indexOf(child); } public int getChildCount() { return secuenciaDefConstanteModelo.getSize(); } public void valueForPathChanged(TreePath path, Object newValue) { 199 Sobre la información, su estructura y su-gestión } public void setModelo(Object obj) { secuenciaDefConstante = (SecuenciaDefConstante) obj; } public void setModeloRecursivo(Object obj) { this.setModelo(obj); for (int i=0; i<secuenciaDefConstante.getSize();i++){ secuenciaDefConstanteModelo.addElement( TreeNode.getTreeNode(secuenciaDefConstante.getDefConstanteAt(i))); } } public String toString(){ return this.cabecera; } } package tfc; import java.util.Vector; import java.util.Enumeration; import javax.swing.event.*; public class SecuenciaDefParametro extends Secuencia { private Vector delegate = new Vector(); public SecuenciaDefParametro() { } public int getSize() { return delegate.size(); } public Object getElementAt(int index) { return delegate.elementAt(index); } public DefParametro getDefParametroAt(int index) { return (DefParametro)delegate.elementAt(index); } public void copyInto(DefParametro anArray[]) { delegate.copyInto(anArray); } public void trimToSize() { delegate.trimToSize(); } public void ensureCapacity(int minCapacity) { delegate.ensureCapacity(minCapacity); } public void setSize(int newSize) { int oldSize = delegate.size(); delegate.setSize(newSize); if (oldSize > newSize) { fireIntervalRemoved(this, newSize, oldSize-1); } else if (oldSize < newSize) { fireIntervalAdded(this, oldSize, newSize-1); } } public int capacity() { return delegate.capacity(); } public int size() { 200 Sobre la información, su estructura y su-gestión return delegate.size(); } public boolean isEmpty() { return delegate.isEmpty(); } public Enumeration elements() { return delegate.elements(); } public boolean contains(DefParametro elem) { return delegate.contains(elem); } public int indexOf(DefParametro elem) { return delegate.indexOf(elem); } public int indexOf(DefParametro elem, int index) { return delegate.indexOf(elem, index); } public int lastIndexOf(DefParametro elem) { return delegate.lastIndexOf(elem); } public int lastIndexOf(DefParametro elem, int index) { return delegate.lastIndexOf(elem, index); } public DefParametro elementAt(int index) { return (DefParametro)delegate.elementAt(index); } public DefParametro firstElement() { return (DefParametro)delegate.firstElement(); } public DefParametro lastElement() { return (DefParametro)delegate.lastElement(); } public void setElementAt(DefParametro obj, int index) { delegate.setElementAt(obj, index); fireContentsChanged(this, index, index); } public void removeElementAt(int index) { delegate.removeElementAt(index); fireIntervalRemoved(this, index, index); } public void insertElementAt(DefParametro obj, int index) { delegate.insertElementAt(obj, index); fireIntervalAdded(this, index, index); } public void addElement(DefParametro obj) { int index = delegate.size(); delegate.addElement(obj); fireIntervalAdded(this, index, index); } public boolean removeElement(DefParametro obj) { int index = indexOf(obj); boolean rv = delegate.removeElement(obj); if (index >= 0) { fireIntervalRemoved(this, index, index); 201 Sobre la información, su estructura y su-gestión } return rv; } public void removeAllElements() { int index1 = delegate.size()-1; delegate.removeAllElements(); if (index1 >= 0) { fireIntervalRemoved(this, 0, index1); } } public String toString() { return delegate.toString(); } public Object[] toArray() { Object[] rv = new Object[delegate.size()]; delegate.copyInto(rv); return rv; } public DefParametro get(int index) { return (DefParametro)delegate.elementAt(index); } public DefParametro set(int index, DefParametro element) { Object rv = delegate.elementAt(index); delegate.setElementAt(element, index); fireContentsChanged(this, index, index); return (DefParametro)rv; } public void add(int index, DefParametro element) { delegate.insertElementAt(element, index); fireIntervalAdded(this, index, index); } public DefParametro remove(int index) { Object rv = delegate.elementAt(index); delegate.removeElementAt(index); fireIntervalRemoved(this, index, index); return (DefParametro)rv; } public void clear() { int index1 = delegate.size()-1; delegate.removeAllElements(); if (index1 >= 0) { fireIntervalRemoved(this, 0, index1); } } public void removeRange(int fromIndex, int toIndex) { for(int i = toIndex; i >= fromIndex; i--) { delegate.removeElementAt(i); } fireIntervalRemoved(this, fromIndex, toIndex); } public void convertirEnEjecutable(java.io.PrintWriter salida, GestorIdentificadores gestor){ for(int i=0; i<this.getSize();i++){ ((DefParametro)this.get(i)).convertirEnEjecutable(salida,gestor); } } public String getRepTextual(){ 202 Sobre la información, su estructura y su-gestión StringBuffer salida = new StringBuffer(); for(int i=0; i<this.size(); i++){ salida.append(this.getDefParametroAt(i).getRepTextual()+", "); } return salida.toString(); } } package tfc; import java.util.Vector; import java.util.Enumeration; import javax.swing.event.*; public class SecuenciaDefSubprograma extends Secuencia { private Vector delegate = new Vector(); public SecuenciaDefSubprograma() { } public int getSize() { return delegate.size(); } public Object getElementAt(int index) { return delegate.elementAt(index); } public DefSubprograma getDefSubprogramaAt(int index) { return (DefSubprograma)delegate.elementAt(index); } public void copyInto(DefSubprograma anArray[]) { delegate.copyInto(anArray); } public void trimToSize() { delegate.trimToSize(); } public void ensureCapacity(int minCapacity) { delegate.ensureCapacity(minCapacity); } public void setSize(int newSize) { int oldSize = delegate.size(); delegate.setSize(newSize); if (oldSize > newSize) { fireIntervalRemoved(this, newSize, oldSize-1); } else if (oldSize < newSize) { fireIntervalAdded(this, oldSize, newSize-1); } } public int capacity() { return delegate.capacity(); } public int size() { return delegate.size(); } public boolean isEmpty() { return delegate.isEmpty(); } public Enumeration elements() { return delegate.elements(); 203 Sobre la información, su estructura y su-gestión } public boolean contains(DefSubprograma elem) { return delegate.contains(elem); } public int indexOf(DefSubprograma elem) { return delegate.indexOf(elem); } public int indexOf(DefSubprograma elem, int index) { return delegate.indexOf(elem, index); } public int lastIndexOf(DefSubprograma elem) { return delegate.lastIndexOf(elem); } public int lastIndexOf(DefSubprograma elem, int index) { return delegate.lastIndexOf(elem, index); } public DefSubprograma elementAt(int index) { return (DefSubprograma)delegate.elementAt(index); } public DefSubprograma firstElement() { return (DefSubprograma)delegate.firstElement(); } public DefSubprograma lastElement() { return (DefSubprograma)delegate.lastElement(); } public void setElementAt(DefSubprograma obj, int index) { delegate.setElementAt(obj, index); fireContentsChanged(this, index, index); } public void removeElementAt(int index) { delegate.removeElementAt(index); fireIntervalRemoved(this, index, index); } public void insertElementAt(DefSubprograma obj, int index) { delegate.insertElementAt(obj, index); fireIntervalAdded(this, index, index); } public void addElement(DefSubprograma obj) { int index = delegate.size(); delegate.addElement(obj); fireIntervalAdded(this, index, index); } public boolean removeElement(DefSubprograma obj) { int index = indexOf(obj); boolean rv = delegate.removeElement(obj); if (index >= 0) { fireIntervalRemoved(this, index, index); } return rv; } public void removeAllElements() { int index1 = delegate.size()-1; delegate.removeAllElements(); if (index1 >= 0) { fireIntervalRemoved(this, 0, index1); 204 Sobre la información, su estructura y su-gestión } } public String toString() { return delegate.toString(); } public Object[] toArray() { Object[] rv = new Object[delegate.size()]; delegate.copyInto(rv); return rv; } public DefSubprograma get(int index) { return (DefSubprograma)delegate.elementAt(index); } public DefSubprograma set(int index, DefSubprograma element) { Object rv = delegate.elementAt(index); delegate.setElementAt(element, index); fireContentsChanged(this, index, index); return (DefSubprograma)rv; } public void add(int index, DefSubprograma element) { delegate.insertElementAt(element, index); fireIntervalAdded(this, index, index); } public DefSubprograma remove(int index) { Object rv = delegate.elementAt(index); delegate.removeElementAt(index); fireIntervalRemoved(this, index, index); return (DefSubprograma)rv; } public void clear() { int index1 = delegate.size()-1; delegate.removeAllElements(); if (index1 >= 0) { fireIntervalRemoved(this, 0, index1); } } public void removeRange(int fromIndex, int toIndex) { for(int i = toIndex; i >= fromIndex; i--) { delegate.removeElementAt(i); } fireIntervalRemoved(this, fromIndex, toIndex); } public void convertirEnEjecutable(java.io.PrintWriter salida, GestorIdentificadores gestor){ for(int i=0; i<this.getSize();i++){ ((DefSubprograma)this.get(i)).convertirEnEjecutable(salida,gestor); } } public String getRepTextual(){ StringBuffer salida = new StringBuffer(); for(int i=0; i<this.size(); i++){ salida.append(this.getDefSubprogramaAt(i).getRepTextual()+", "); } return salida.toString(); } } package tfc; 205 Sobre la información, su estructura y su-gestión import javax.swing.DefaultListModel; import javax.swing.tree.TreePath; public class SecuenciaDefSubprogramaTreeNode extends TreeNode { SecuenciaDefSubprograma secuenciaDefSubprograma; DefaultListModel secuenciaDefSubprogramaModelo = new DefaultListModel(); public ElementoCodigoInterfaz getElementoCodigo(){ return secuenciaDefSubprograma; } public SecuenciaDefSubprogramaTreeNode(SecuenciaDefSubprograma secuenciaDefSubprograma) { this.setModeloRecursivo(secuenciaDefSubprograma); } public SecuenciaDefSubprogramaTreeNode() { } public boolean isLeaf() { return false; } public Object getChild(int index) { return secuenciaDefSubprogramaModelo.get(index); } public int getIndexOfChild(Object child) { return secuenciaDefSubprogramaModelo.indexOf(child); } public int getChildCount() { return secuenciaDefSubprogramaModelo.getSize(); } public void valueForPathChanged(TreePath path, Object newValue) { } public void setModelo(Object obj) { secuenciaDefSubprograma = (SecuenciaDefSubprograma) obj; } public void setModeloRecursivo(Object obj) { this.setModelo(obj); for (int i=0; i<secuenciaDefSubprograma.getSize();i++){ secuenciaDefSubprogramaModelo.addElement( TreeNode.getTreeNode( secuenciaDefSubprograma.getDefSubprogramaAt(i))); } } public String toString(){ return cabecera; } } package tfc; import java.util.Vector; import java.util.Enumeration; import javax.swing.event.*; public class SecuenciaDefTipo extends Secuencia { private Vector delegate = new Vector(); public SecuenciaDefTipo() { } public int getSize() { return delegate.size(); } public Object getElementAt(int index) { return delegate.elementAt(index); } 206 Sobre la información, su estructura y su-gestión public DefTipo getDefTipoAt(int index) { return (DefTipo)delegate.elementAt(index); } public void copyInto(DefTipo anArray[]) { delegate.copyInto(anArray); } public void trimToSize() { delegate.trimToSize(); } public void ensureCapacity(int minCapacity) { delegate.ensureCapacity(minCapacity); } public void setSize(int newSize) { int oldSize = delegate.size(); delegate.setSize(newSize); if (oldSize > newSize) { fireIntervalRemoved(this, newSize, oldSize-1); } else if (oldSize < newSize) { fireIntervalAdded(this, oldSize, newSize-1); } } public int capacity() { return delegate.capacity(); } public int size() { return delegate.size(); } public boolean isEmpty() { return delegate.isEmpty(); } public Enumeration elements() { return delegate.elements(); } public boolean contains(DefTipo elem) { return delegate.contains(elem); } public int indexOf(DefTipo elem) { return delegate.indexOf(elem); } public int indexOf(DefTipo elem, int index) { return delegate.indexOf(elem, index); } public int lastIndexOf(DefTipo elem) { return delegate.lastIndexOf(elem); } public int lastIndexOf(DefTipo elem, int index) { return delegate.lastIndexOf(elem, index); } public DefTipo elementAt(int index) { return (DefTipo)delegate.elementAt(index); } public DefTipo firstElement() { return (DefTipo)delegate.firstElement(); 207 Sobre la información, su estructura y su-gestión } public DefTipo lastElement() { return (DefTipo)delegate.lastElement(); } public void setElementAt(DefTipo obj, int index) { delegate.setElementAt(obj, index); fireContentsChanged(this, index, index); } public void removeElementAt(int index) { delegate.removeElementAt(index); fireIntervalRemoved(this, index, index); } public void insertElementAt(DefTipo obj, int index) { delegate.insertElementAt(obj, index); fireIntervalAdded(this, index, index); } public void addElement(DefTipo obj) { int index = delegate.size(); delegate.addElement(obj); fireIntervalAdded(this, index, index); } public boolean removeElement(DefTipo obj) { int index = indexOf(obj); boolean rv = delegate.removeElement(obj); if (index >= 0) { fireIntervalRemoved(this, index, index); } return rv; } public void removeAllElements() { int index1 = delegate.size()-1; delegate.removeAllElements(); if (index1 >= 0) { fireIntervalRemoved(this, 0, index1); } } public String toString() { return delegate.toString(); } public Object[] toArray() { Object[] rv = new Object[delegate.size()]; delegate.copyInto(rv); return rv; } public DefTipo get(int index) { return (DefTipo)delegate.elementAt(index); } public DefTipo set(int index, DefTipo element) { Object rv = delegate.elementAt(index); delegate.setElementAt(element, index); fireContentsChanged(this, index, index); return (DefTipo)rv; } public void add(int index, DefTipo element) { delegate.insertElementAt(element, index); fireIntervalAdded(this, index, index); } 208 Sobre la información, su estructura y su-gestión public DefTipo remove(int index) { Object rv = delegate.elementAt(index); delegate.removeElementAt(index); fireIntervalRemoved(this, index, index); return (DefTipo)rv; } public void clear() { int index1 = delegate.size()-1; delegate.removeAllElements(); if (index1 >= 0) { fireIntervalRemoved(this, 0, index1); } } public void removeRange(int fromIndex, int toIndex) { for(int i = toIndex; i >= fromIndex; i--) { delegate.removeElementAt(i); } fireIntervalRemoved(this, fromIndex, toIndex); } public String getRepTextual(){ StringBuffer salida = new StringBuffer(); for(int i=0; i<this.size(); i++){ salida.append(this.getDefTipoAt(i).getRepTextual()+", "); } return salida.toString(); } } package tfc; import javax.swing.DefaultListModel; import javax.swing.tree.TreePath; public class SecuenciaDefTipoTreeNode extends TreeNode { SecuenciaDefTipo secuenciaDefTipo; DefaultListModel secuenciaDefTipoModelo = new DefaultListModel(); public ElementoCodigoInterfaz getElementoCodigo(){ return secuenciaDefTipo; } public SecuenciaDefTipoTreeNode(SecuenciaDefTipo secuenciaDefTipo) { this.setModeloRecursivo(secuenciaDefTipo); setCabecera("Tipos"); } public SecuenciaDefTipoTreeNode() { setCabecera("Tipos"); } public boolean isLeaf() { return false; } public Object getChild(int index) { return secuenciaDefTipoModelo.get(index); } public int getIndexOfChild(Object child) { return secuenciaDefTipoModelo.indexOf(child); } public int getChildCount() { return secuenciaDefTipoModelo.getSize(); } public void valueForPathChanged(TreePath path, Object newValue) { } public void setModelo(Object obj) { secuenciaDefTipo = (SecuenciaDefTipo) obj; } public void setModeloRecursivo(Object obj) { 209 Sobre la información, su estructura y su-gestión this.setModelo(obj); for (int i=0; i<secuenciaDefTipo.getSize();i++){ secuenciaDefTipoModelo.addElement( TreeNode.getTreeNode( secuenciaDefTipo.getDefTipoAt(i))); } } public String toString(){ return cabecera; } } package tfc; import java.util.Vector; import java.util.Enumeration; import javax.swing.event.*; public class SecuenciaDefVariable extends Secuencia { private Vector delegate = new Vector(); public SecuenciaDefVariable() { } public int getSize() { return delegate.size(); } public Object getElementAt(int index) { return delegate.elementAt(index); } public DefVariable getDefVariableAt(int index) { return (DefVariable)delegate.elementAt(index); } public void copyInto(DefVariable anArray[]) { delegate.copyInto(anArray); } public void trimToSize() { delegate.trimToSize(); } public void ensureCapacity(int minCapacity) { delegate.ensureCapacity(minCapacity); } public void setSize(int newSize) { int oldSize = delegate.size(); delegate.setSize(newSize); if (oldSize > newSize) { fireIntervalRemoved(this, newSize, oldSize-1); } else if (oldSize < newSize) { fireIntervalAdded(this, oldSize, newSize-1); } } public int capacity() { return delegate.capacity(); } public int size() { return delegate.size(); } public boolean isEmpty() { return delegate.isEmpty(); } 210 Sobre la información, su estructura y su-gestión public Enumeration elements() { return delegate.elements(); } public boolean contains(DefVariable elem) { return delegate.contains(elem); } public int indexOf(DefVariable elem) { return delegate.indexOf(elem); } public int indexOf(DefVariable elem, int index) { return delegate.indexOf(elem, index); } public int lastIndexOf(DefVariable elem) { return delegate.lastIndexOf(elem); } public int lastIndexOf(DefVariable elem, int index) { return delegate.lastIndexOf(elem, index); } public DefVariable elementAt(int index) { return (DefVariable)delegate.elementAt(index); } public DefVariable firstElement() { return (DefVariable)delegate.firstElement(); } public DefVariable lastElement() { return (DefVariable)delegate.lastElement(); } public void setElementAt(DefVariable obj, int index) { delegate.setElementAt(obj, index); fireContentsChanged(this, index, index); } public void removeElementAt(int index) { delegate.removeElementAt(index); fireIntervalRemoved(this, index, index); } public void insertElementAt(DefVariable obj, int index) { delegate.insertElementAt(obj, index); fireIntervalAdded(this, index, index); } public void addElement(DefVariable obj) { int index = delegate.size(); delegate.addElement(obj); fireIntervalAdded(this, index, index); } public boolean removeElement(DefVariable obj) { int index = indexOf(obj); boolean rv = delegate.removeElement(obj); if (index >= 0) { fireIntervalRemoved(this, index, index); } return rv; } public void removeAllElements() { int index1 = delegate.size()-1; 211 Sobre la información, su estructura y su-gestión delegate.removeAllElements(); if (index1 >= 0) { fireIntervalRemoved(this, 0, index1); } } public String toString() { return delegate.toString(); } public Object[] toArray() { Object[] rv = new Object[delegate.size()]; delegate.copyInto(rv); return rv; } public DefVariable get(int index) { return (DefVariable)delegate.elementAt(index); } public DefVariable set(int index, DefVariable element) { Object rv = delegate.elementAt(index); delegate.setElementAt(element, index); fireContentsChanged(this, index, index); return (DefVariable)rv; } public void add(int index, DefVariable element) { delegate.insertElementAt(element, index); fireIntervalAdded(this, index, index); } public DefVariable remove(int index) { Object rv = delegate.elementAt(index); delegate.removeElementAt(index); fireIntervalRemoved(this, index, index); return (DefVariable)rv; } public void clear() { int index1 = delegate.size()-1; delegate.removeAllElements(); if (index1 >= 0) { fireIntervalRemoved(this, 0, index1); } } public String getRepTextual(){ StringBuffer salida = new StringBuffer(); for(int i=0; i<this.size(); i++){ salida.append(this.getDefVariableAt(i).getRepTextual()+", "); } return salida.toString(); } public void removeRange(int fromIndex, int toIndex) { for(int i = toIndex; i >= fromIndex; i--) { delegate.removeElementAt(i); } fireIntervalRemoved(this, fromIndex, toIndex); } public void convertirEnEjecutable(java.io.PrintWriter salida, GestorIdentificadores gestor){ for(int i=0; i<this.getSize();i++){ ((DefVariable)this.get(i)).convertirEnEjecutable(salida,gestor); } } 212 Sobre la información, su estructura y su-gestión } package tfc; public class SecuenciaDefVariableEjecucion extends Secuencia { public SecuenciaDefVariableEjecucion() { } public ItemDeInformacion getDefVariableEjecucionAt(int index) { return (ItemDeInformacion)delegate.elementAt(index); } public void copyInto(ItemDeInformacion anArray[]) { delegate.copyInto(anArray); } public boolean contains(ItemDeInformacion elem) { return delegate.contains(elem); } public int indexOf(ItemDeInformacion elem) { return delegate.indexOf(elem); } public int indexOf(ItemDeInformacion elem, int index) { return delegate.indexOf(elem, index); } public int lastIndexOf(ItemDeInformacion elem) { return delegate.lastIndexOf(elem); } public int lastIndexOf(ItemDeInformacion elem, int index) { return delegate.lastIndexOf(elem, index); } public ItemDeInformacion elementAt(int index) { return (ItemDeInformacion)delegate.elementAt(index); } public ItemDeInformacion firstElement() { return (ItemDeInformacion)delegate.firstElement(); } public ItemDeInformacion lastElement() { return (ItemDeInformacion)delegate.lastElement(); } // // // public void setElementAt(ItemDeInformacion obj, int index) { delegate.setElementAt(obj, index); fireContentsChanged(this, index, index); } public void insertElementAt(ItemDeInformacion obj, int index) { delegate.insertElementAt(obj, index); fireIntervalAdded(this, index, index); } public void addElement(ItemDeInformacion obj) { int index = delegate.size(); delegate.addElement(obj); fireIntervalAdded(this, index, index); } public boolean removeElement(ItemDeInformacion obj) { int index = indexOf(obj); 213 Sobre la información, su estructura y su-gestión boolean rv = delegate.removeElement(obj); if (index >= 0) { fireIntervalRemoved(this, index, index); } return rv; // } public ItemDeInformacion get(int index) { return (ItemDeInformacion)delegate.elementAt(index); } // // // // public ItemDeInformacion set(int index, ItemDeInformacion element) { Object rv = delegate.elementAt(index); delegate.setElementAt(element, index); fireContentsChanged(this, index, index); return (ItemDeInformacion)rv; } public void add(int index, ItemDeInformacion element) { delegate.insertElementAt(element, index); fireIntervalAdded(this, index, index); } public ItemDeInformacion remove(int index) { Object rv = delegate.elementAt(index); delegate.removeElementAt(index); fireIntervalRemoved(this, index, index); return (ItemDeInformacion)rv; } public void libera(java.io.PrintWriter salida){ for (int i=0; i<this.size();i++){ this.getDefVariableEjecucionAt(i).libera(); (this.remove(i)).liberacion(salida); } } } package tfc; import javax.swing.DefaultListModel; import javax.swing.tree.TreePath; public class SecuenciaDefVariableTreeNode extends TreeNode { SecuenciaDefVariable secuenciaDefVariable; DefaultListModel secuenciaDefVariableModelo = new DefaultListModel(); public ElementoCodigoInterfaz getElementoCodigo(){ return secuenciaDefVariable; } public SecuenciaDefVariableTreeNode(SecuenciaDefVariable secuenciaDefVariable) { this.setModeloRecursivo(secuenciaDefVariable); setCabecera("Variables"); } public SecuenciaDefVariableTreeNode() { setCabecera("Variables"); } public boolean isLeaf() { return false; } public Object getChild(int index) { return secuenciaDefVariableModelo.get(index); } public int getIndexOfChild(Object child) { return secuenciaDefVariableModelo.indexOf(child); } public int getChildCount() { return secuenciaDefVariableModelo.getSize(); } 214 Sobre la información, su estructura y su-gestión public void valueForPathChanged(TreePath path, Object newValue) { } public void setModelo(Object obj) { secuenciaDefVariable = (SecuenciaDefVariable) obj; } public void setModeloRecursivo(Object obj) { this.setModelo(obj); for (int i=0; i<secuenciaDefVariable.getSize();i++){ secuenciaDefVariableModelo.addElement( TreeNode.getTreeNode(secuenciaDefVariable.getDefVariableAt(i))); } } public String toString(){ return cabecera; } } package tfc; import java.util.Vector; import java.util.Enumeration; public abstract class SecuenciaEjecucion extends Ejecucion { int nElementos; public int getNElementos(){ return nElementos; } public void setNElementos(int nElementos){ this.nElementos = nElementos; } } package tfc; import java.util.Vector; import java.util.Enumeration; import javax.swing.event.*; public class SecuenciaEjecutable extends Secuencia { public SecuenciaEjecutable() { } public int getSize() { return delegate.size(); } public Object getElementAt(int index) { return delegate.elementAt(index); } public Sentencia getEjecutableAt(int index) { return (Sentencia)delegate.elementAt(index); } public void copyInto(Sentencia anArray[]) { delegate.copyInto(anArray); } public void trimToSize() { delegate.trimToSize(); } public void ensureCapacity(int minCapacity) { delegate.ensureCapacity(minCapacity); } 215 Sobre la información, su estructura y su-gestión public void setSize(int newSize) { int oldSize = delegate.size(); delegate.setSize(newSize); if (oldSize > newSize) { fireIntervalRemoved(this, newSize, oldSize-1); } else if (oldSize < newSize) { fireIntervalAdded(this, oldSize, newSize-1); } } public int capacity() { return delegate.capacity(); } public int size() { return delegate.size(); } public boolean isEmpty() { return delegate.isEmpty(); } public Enumeration elements() { return delegate.elements(); } public boolean contains(Sentencia elem) { return delegate.contains(elem); } public int indexOf(Sentencia elem) { return delegate.indexOf(elem); } public int indexOf(Sentencia elem, int index) { return delegate.indexOf(elem, index); } public int lastIndexOf(Sentencia elem) { return delegate.lastIndexOf(elem); } public int lastIndexOf(Sentencia elem, int index) { return delegate.lastIndexOf(elem, index); } public Sentencia elementAt(int index) { return (Sentencia)delegate.elementAt(index); } public Sentencia firstElement() { return (Sentencia)delegate.firstElement(); } public Sentencia lastElement() { return (Sentencia)delegate.lastElement(); } public void setElementAt(Sentencia obj, int index) { delegate.setElementAt(obj, index); fireContentsChanged(this, index, index); } public void removeElementAt(int index) { delegate.removeElementAt(index); fireIntervalRemoved(this, index, index); } 216 Sobre la información, su estructura y su-gestión public void insertElementAt(Sentencia obj, int index) { delegate.insertElementAt(obj, index); fireIntervalAdded(this, index, index); } public void addElement(Sentencia obj) { int index = delegate.size(); delegate.addElement(obj); fireIntervalAdded(this, index, index); } public boolean removeElement(Sentencia obj) { int index = indexOf(obj); boolean rv = delegate.removeElement(obj); if (index >= 0) { fireIntervalRemoved(this, index, index); } return rv; } public void removeAllElements() { int index1 = delegate.size()-1; delegate.removeAllElements(); if (index1 >= 0) { fireIntervalRemoved(this, 0, index1); } } public String toString() { return delegate.toString(); } public Object[] toArray() { Object[] rv = new Object[delegate.size()]; delegate.copyInto(rv); return rv; } public Sentencia get(int index) { return (Sentencia)delegate.elementAt(index); } public Sentencia set(int index, Sentencia element) { Object rv = delegate.elementAt(index); delegate.setElementAt(element, index); fireContentsChanged(this, index, index); return (Sentencia)rv; } public void add(int index, Sentencia element) { delegate.insertElementAt(element, index); fireIntervalAdded(this, index, index); } public Sentencia remove(int index) { Object rv = delegate.elementAt(index); delegate.removeElementAt(index); fireIntervalRemoved(this, index, index); return (Sentencia)rv; } public void clear() { int index1 = delegate.size()-1; delegate.removeAllElements(); if (index1 >= 0) { fireIntervalRemoved(this, 0, index1); } } 217 Sobre la información, su estructura y su-gestión public void removeRange(int fromIndex, int toIndex) { for(int i = toIndex; i >= fromIndex; i--) { delegate.removeElementAt(i); } fireIntervalRemoved(this, fromIndex, toIndex); } public void resuelveReferencias(GestorAmbito ambito, ErroresReferencias errores){ for(int i=0; i<this.size();i++){ this.getEjecutableAt(i).resuelveReferencias(ambito,errores); } } } package tfc; public abstract class SecuenciaEjecutableEjecucion extends SecuenciaEjecucion implements EjecutableEjecucionI { public SecuenciaEjecutableEjecucion() { } public void ejecuta(InformacionAccesible inf, java.io.PrintWriter salida){ for(int i=0; i<this.getNElementos();i++){ this.ejecutaYFin(i,inf,salida); } } public void fin(){ } protected abstract void ejecutaYFin(int i, InformacionAccesible inf, java.io.PrintWriter salida); } package tfc; public interface Sentencia extends ElementoCodigoEjecucion { public String getRepTextual(); } package tfc; public abstract class TipoCompuesto extends DefTipo { } package tfc; public abstract class TipoSimple extends DefTipo { public ValorSimple getValor(String representacion){ return new ValorSimple(representacion,this); } public void resuelveReferencias(GestorAmbito ambito, ErroresReferencias errores){ } } package tfc; public class TiposIncompatiblesException extends Exception { public TiposIncompatiblesException() { } 218 Sobre la información, su estructura y su-gestión } package tfc; import javax.swing.tree.*; public abstract class TreeNode implements TreeNodeI { String cabecera = ""; public public public public public abstract void setModelo(Object obj); abstract void setModeloRecursivo(Object obj); abstract boolean isLeaf(); abstract int getChildCount(); void setCabecera(String cabecera){ this.cabecera = cabecera; } public static TreeNodeI getTreeNode( ElementoCodigoInterfaz elemento){ Class clase; Class claseTreeNode; TreeNodeI salida = null; try { clase = elemento.getClass(); claseTreeNode = Class.forName( clase.getName() + "TreeNode" ); salida = (TreeNodeI)claseTreeNode.newInstance(); salida.setModeloRecursivo( elemento ); } catch (ClassNotFoundException e){ e.printStackTrace(); } catch (IllegalAccessException e){ e.printStackTrace(); } catch (InstantiationException e){ e.printStackTrace(); } return salida; } public abstract ElementoCodigoInterfaz getElementoCodigo(); public String toString(){ return cabecera + this.getElementoCodigo().toString(); } } package tfc; import javax.swing.tree.TreePath; public interface TreeNodeI { public abstract void setModelo(Object obj); public abstract void setModeloRecursivo(Object obj); public abstract boolean isLeaf(); public abstract int getIndexOfChild(Object child); public abstract void valueForPathChanged(TreePath path, Object newValue); public abstract int getChildCount(); public abstract Object getChild(int index); } package tfc; public abstract class Valor extends ElementoCodigo implements Evaluable { DefTipo defTipo; public DefTipo getDefTipo(){ 219 Sobre la información, su estructura y su-gestión return defTipo; } public void setDefTipo(DefTipo defTipo){ this.defTipo = defTipo; } public void resuelveReferencias(GestorAmbito ambito, ErroresReferencias errores){ } public boolean equals(Valor v){ return (this.getDefTipo() == v.getDefTipo()); } } package tfc; public class ValorOrdinal extends ValorSimple { public ValorOrdinal(String representacion, DefTipo defTipo){ super(representacion,defTipo); } } package tfc; public class ValorReal extends ValorSimple { public ValorReal(String representacion) { super(representacion, Real.getInstance() ); } public double getDouble(){ return Double.parseDouble(representacion); } } package tfc; public class ValorSimple extends Valor { String representacion; public ValorSimple(String representacion, DefTipo defTipo){ this.representacion = representacion; this.setDefTipo(defTipo); } public String getRepTextual(){ return representacion; } String getRepresentacion(){ return representacion; } public boolean equals(ValorSimple v){ return super.equals(v) && ( this.getRepresentacion().equals( v.getRepresentacion() )); } public void convertirEnEjecutable(java.io.PrintWriter salida, GestorIdentificadores gestor){ this.setCodigo(gestor); salida.println("//-------------------------------------------------------------------"); 220 Sobre la información, su estructura y su-gestión salida.println("class "+this.getNombreEjecucion()+" implements EvaluableEjecucionI {" ); salida.println(" public "+this.getNombreEjecucion()+"() {}"); salida.println(" public Valor evaluate(InformacionAccesible i, java.io.PrintWriter salida){"); salida.println(" return Entero.getInstance().getValor(\""+this.getRepTextual()+"\");"); salida.println(" }"); salida.println("}"); } } package tfc; import javax.swing.tree.*; public class ValorSimpleTreeNode extends TreeNode implements EvaluableTreeNodeI { ValorSimple valorSimple; public ElementoCodigoInterfaz getElementoCodigo(){ return valorSimple; } public ValorSimpleTreeNode(ValorSimple valorSimple) { this.setModeloRecursivo(valorSimple); setCabecera("Valor Simple -> "); } public ValorSimpleTreeNode() { setCabecera("Valor Simple -> "); } public boolean isLeaf() { return true; } public Object getChild(int index) { return null; } public int getIndexOfChild(Object child) { return -1; } public int getChildCount() { return 0; } public void valueForPathChanged(TreePath path, Object newValue) { } public void setModelo(Object obj) { valorSimple = (ValorSimple) obj; } public void setModeloRecursivo(Object obj){ this.setModelo(obj); } public String toString(){ return cabecera + valorSimple.getRepTextual(); } } package tfc; import javax.swing.tree.*; public class ValorTreeNode extends TreeNode { 221 Sobre la información, su estructura y su-gestión Valor valor; public ValorTreeNode() { } public ElementoCodigoInterfaz getElementoCodigo(){ return valor; } public boolean isLeaf() { return true; } public Object getChild(int index) { return null; } public int getIndexOfChild(Object child) { return -1; } public int getChildCount() { return 0; } public void valueForPathChanged(TreePath path, Object newValue) { } public void setModelo(Object obj) { this.valor = (Valor) obj; } public void setModeloRecursivo(Object obj){ this.setModelo(obj); } public String toString(){ return cabecera + valor.getRepTextual(); } } 222 Anexo B. Especificación en JavaCC options { STATIC = true; IGNORE_CASE = true; } PARSER_BEGIN(Pascal) package tfc; import javax.swing.JTextArea; import javax.swing.DefaultListModel; import java.util.Vector; public class Pascal { } PARSER_END(Pascal) SKIP : { " " | "\t" | "\n" | "\r" } TOKEN : { | | | | | | | | | | | | <AND: "AND"> <CONST: "CONST"> <ELSE: "ELSE"> <MOD:"MOD"> <OR:"OR"> <RECORD:"RECORD"> <VAR:"VAR"> <ARRAY:"ARRAY"> <DIV:"DIV"> <END:"END"> <NOT:"NOT"> <PROCEDURE:"PROCEDURE"> <FUNCTION:"FUNCTION"> 223 Sobre la información, su estructura y su-gestión | | | | | | | | | | <THEN:"THEN"> <WHILE:"WHILE"> <BEGIN:"BEGIN"> <DO:"DO"> <IF:"IF"> <OF:"OF"> <PROGRAM:"PROGRAM"> <TYPE:"TYPE"> <ID: ["a"-"z","A"-"Z"] ( ["a"-"z","A"-"Z","0"-"9"] )*> <NUMERO: (["0"-"9"])+ > } DefPrograma programa() : { Nombre n; DefPrograma defPrograma; } { <PROGRAM> n=id() ";" { defPrograma = new DefPrograma(n); } bloque(defPrograma) "." { return defPrograma; } } void bloque(DefPrograma defPrograma) : { InstruccionCompuesta instruccion; } { declaracion(defPrograma) instruccion = grupoSent(defPrograma.getAmbito()) { defPrograma.setCuerpo(instruccion); } } void declaracion(DefPrograma defPrograma) : {} { [ defCte(defPrograma.getConstantes(),defPrograma.getAmbito()) ] [ defTip(defPrograma.getTipos(),defPrograma.getAmbito()) ] [ defVar(defPrograma.getVariables(),defPrograma.getAmbito()) ] [ defSubprograma(defPrograma.getSubprogramas(),defPrograma) ] } void defSubprograma(SecuenciaDefSubprograma secuenciaDefSubprograma, AccesoAmbito ambito) : { DefSubprograma defSubprograma; } { ( ( defSubprograma = defProcedimiento(ambito) | defSubprograma = defFuncion(ambito) ) { secuenciaDefSubprograma.addElement(defSubprograma); } )+ } void defCte(SecuenciaDefConstante secuenciaDefConstante, GestorAmbito ambito) : {} { <CONST> (unaConst(secuenciaDefConstante,ambito) ";")+ } void unaConst(SecuenciaDefConstante secuenciaDefConstante, GestorAmbito ambito) : { Token t; Valor v; } { t=<ID> "=" v=valorSimple() { secuenciaDefConstante.addElement(new DefConstante(new Nombre(t.image),v));} 224 Sobre la información, su estructura y su-gestión } void defTip(SecuenciaDefTipo secuenciaDefTipo, GestorAmbito ambito) : {} { <TYPE> (unTipo() ";")+ } void unTipo() : {} { <ID> "=" tipoEstr() } void tipoEstr() : {} { ( estrTab() | estrReg() ) } void estrTab() : {} { <ARRAY> "[" valorSimple() ".." valorSimple() "]" <OF> <ID> } void estrReg() : {} { <RECORD> listaVar(null) ( ";" listaVar(null) )* <END> } void defVar(SecuenciaDefVariable secuenciaDefVariable, GestorAmbito ambito) : {} { <VAR> ( listaVar(secuenciaDefVariable) ";" )+ } DefProcedimiento defProcedimiento(AccesoAmbito ambito) : { Nombre n; DefProcedimiento procedimiento; } { <PROCEDURE> n=id() { procedimiento = new DefProcedimiento(n,ambito); } [ "(" parFormales(procedimiento.getParametros()) ")" ] ";" bloque(procedimiento) ";" { return procedimiento; } } DefFuncion defFuncion(AccesoAmbito ambito) : { Nombre n; DefFuncion funcion; } { <FUNCTION> n=id() { funcion = new DefFuncion(n,ambito); } [ "(" parFormales(funcion.getParametros()) ")" ] ":" n=id() { funcion.setTipo( new RefTipo(n)); }";" bloque(funcion) ";" { return funcion; } } void parFormales(SecuenciaDefParametro secuenciaDefParametro) : {} { listaParam(secuenciaDefParametro) ( ";" listaParam(secuenciaDefParametro) )* } void listaParam(SecuenciaDefParametro parametros) : { Nombre n; Vector nombres = new Vector(); boolean pasoPorValor = true; } 225 Sobre la información, su estructura y su-gestión { [ <VAR> { pasoPorValor = false;} ] n=id() { nombres.add(n); }( "," n=id() { nombres.add(n); } )* ":" n=id() { for(int i=0; i<nombres.size(); i++){ parametros.addElement(new DefParametro((Nombre)nombres.get(i),new RefTipo(n),pasoPorValor)); } } } void listaVar(SecuenciaDefVariable variables) : { Nombre n; Vector nombres = new Vector(); } { n=id() { nombres.add(n); }( "," n=id() { nombres.add(n); } )* ":" n=id() { for(int i=0; i<nombres.size(); i++){ variables.addElement(new DefVariable((Nombre)nombres.get(i),new RefTipo(n))); } } } InstruccionCompuesta grupoSent(GestorAmbito ambito) : { InstruccionCompuesta instruccion = new InstruccionCompuesta(); SecuenciaEjecutable secuencia; Ejecutable ejecutable; } { { secuencia = instruccion.getInstrucciones(); } <BEGIN> ejecutable = sentencia(ambito) { secuencia.addElement(ejecutable); } ( ";" ejecutable = sentencia(ambito) { secuencia.addElement(ejecutable); } )* <END> { return instruccion; } } Ejecutable sentencia(GestorAmbito ambito) : { Ejecutable ejecutable = null; boolean sentenciaVacia = true; } { [ ( LOOKAHEAD(2) ejecutable = asignacion(ambito) | ejecutable = condicional(ambito) | ejecutable = ciclica(ambito) | ejecutable = llamada(ambito) | ejecutable = grupoSent(ambito)) { sentenciaVacia = false; }] { if (sentenciaVacia) { ejecutable = new InstruccionVacia(); } return ejecutable; } } Asignacion asignacion(GestorAmbito ambito) : { Evaluable evaluable; Nombre n; } { n = id() ":=" evaluable = expresion(ambito) { return new Asignacion(new RefVariable(n),evaluable); } } Ejecutable condicional(GestorAmbito ambito) : 226 Sobre la información, su estructura y su-gestión { Condicional condicional; Evaluable evaluable; Ejecutable ejecutable; } { <IF> evaluable = expresion(ambito) <THEN> ejecutable = sentencia(ambito) { condicional = new Condicional(evaluable,ejecutable); } [ LOOKAHEAD(1) <ELSE> ejecutable = sentencia(ambito) { condicional.setParteElse(ejecutable); } ] {return condicional;} } Ejecutable ciclica(GestorAmbito ambito) : {} { <WHILE> expresion(ambito) <DO> sentencia(ambito) {return null;} } RefProcedimiento llamada(GestorAmbito ambito) : { Nombre n; RefProcedimiento refProcedimiento; } { n = id() { refProcedimiento = new RefProcedimiento(n); }[ "(" parLlamada(refProcedimiento.getParamActuales(),ambito) ")" ] { return refProcedimiento; } } void parLlamada(ParametrosActuales paramActuales, GestorAmbito ambito) : { Referencia referencia; Evaluable evaluable = null; } { ( evaluable = expresion(ambito) { if (evaluable instanceof RefVariable){ paramActuales.addRefVariable((RefVariable)evaluable); } else { paramActuales.addEvaluable(evaluable); } } ) ( "," evaluable = expresion(ambito) { if (evaluable instanceof RefVariable){ paramActuales.addRefVariable((RefVariable)evaluable); } else { paramActuales.addEvaluable(evaluable); } } )* } Evaluable expresion(GestorAmbito ambito) : { Evaluable evaluable1 = null; Evaluable evaluable2 = null; Nombre signo = null; } { evaluable1 = exprSimple(ambito) [ signo = opRel() evaluable2 = exprSimple(ambito) ] { if (signo == null) { return evaluable1; } else { RefFuncion refFuncion = new RefFuncionOperador(signo); ParametrosActuales param = refFuncion.getParamActuales(); param.addEvaluable(evaluable1); param.addEvaluable(evaluable2); return refFuncion; 227 Sobre la información, su estructura y su-gestión } } } Evaluable exprSimple(GestorAmbito ambito) : { Evaluable evaluable1; Evaluable evaluable2; Nombre signo = null; } { // El lookahead este está porque hay un factor puede ser una constante con signo, pero poniendo // el lookahead(2) mira si termino corresponde o no a una constante y en este caso lo asocia a // la constante. [ LOOKAHEAD(2) signo=signo() ] evaluable1 = termino(ambito) { if (signo != null && !signo.toString().equals("+")){ RefFuncion refFuncion = new RefFuncionOperador(signo); refFuncion.getParamActuales().addEvaluable(evaluable1); evaluable1 = refFuncion; } } // Ojo con la asociatividad, no recuerdo si era a la derecha o a la izquierda. La verdad es que no // recuerdo exáctamente que significaba cada cosa. Bueno, pongo que se vayan asociando empezando // en la izquierda. ( LOOKAHEAD(2) signo=opAdt() evaluable2 = termino(ambito) { RefFuncion refFuncion = new RefFuncionOperador(signo); refFuncion.getParamActuales().addEvaluable(evaluable1); refFuncion.getParamActuales().addEvaluable(evaluable2); evaluable1 = refFuncion; } )* { return evaluable1; } } Evaluable termino(GestorAmbito ambito) : { Evaluable evaluable1; Evaluable evaluable2; Nombre signo; } { evaluable1 = factor(ambito) ( signo=opMul() evaluable2 = factor(ambito) { RefFuncion refFuncion = new RefFuncionOperador(signo); refFuncion.getParamActuales().addEvaluable(evaluable1); refFuncion.getParamActuales().addEvaluable(evaluable2); evaluable1 = refFuncion; } )* { return evaluable1; } } Evaluable factor(GestorAmbito ambito) : { Referencia referencia; Evaluable evaluable; Token t; } { ( LOOKAHEAD(2) evaluable = valorSimple() 228 Sobre la información, su estructura y su-gestión | evaluable = nombreEval(ambito) | t=<NOT> evaluable = factor(ambito) { RefFuncion refFuncion; refFuncion = new RefFuncionOperador(new Nombre(t.image)); refFuncion.getParamActuales().addEvaluable(evaluable); evaluable = refFuncion; } | "(" evaluable = expresion(ambito) ")" ) { return evaluable; } } Evaluable nombreEval(GestorAmbito ambito) : { Nombre n; } { n = id() { try { return EvaluableInstancia.getEvaluable(n,ambito); } catch(DefinicionNoEncontradaException e2) { throw new ParseException("No encontrado una definicion para el nombre "+n.toString()); } } // Ya pondremos acceso a arrays [ selector(ambito) ] } void selector(GestorAmbito ambito) : {} { "[" expresion(ambito) "]" | "." <ID> } ValorSimple valorSimple() : { Token t; Nombre signo = null; } { [ signo=signo() ] t=<NUMERO> { return Entero.getInstance().getValor( ((signo == null)? "":signo.toString()) + t.image ); } } Nombre opRel() : { Token t; } { (t="<" | t=">" | t="<=" | t=">=" | t="=" | t="<>") { return new Nombre(t.image); } } Nombre opAdt() : { Token t; Nombre n; } { ( n=signo() | t="OR" { n = new Nombre(t.image); } ) { return n; } } Nombre opMul() : { Token t; } { (t="*" | t="DIV" | t="MOD" | t="AND") { return new Nombre(t.image); } } 229 Sobre la información, su estructura y su-gestión Nombre signo() : { Token t; } { (t="+" | t="-") { return new Nombre(t.image); } } Nombre id() : { Token t; } { t=<ID> { return new Nombre(t.image);} } 230 Anexo C. Diagramas UML En este anexo se muestra un diagrama de cada una de las clases mas representativas. 231 Sobre la información, su estructura y su-gestión 232 Sobre la información, su estructura y su-gestión 233 Sobre la información, su estructura y su-gestión 234 Sobre la información, su estructura y su-gestión 235 Sobre la información, su estructura y su-gestión 236 Sobre la información, su estructura y su-gestión 237 Sobre la información, su estructura y su-gestión 238 Sobre la información, su estructura y su-gestión 239 Sobre la información, su estructura y su-gestión 240 Sobre la información, su estructura y su-gestión 241 Sobre la información, su estructura y su-gestión 242 Sobre la información, su estructura y su-gestión 243 Sobre la información, su estructura y su-gestión 244 Sobre la información, su estructura y su-gestión 245 Sobre la información, su estructura y su-gestión 246 Sobre la información, su estructura y su-gestión 247 Sobre la información, su estructura y su-gestión 248 Sobre la información, su estructura y su-gestión 249 Sobre la información, su estructura y su-gestión 250 Sobre la información, su estructura y su-gestión 251 Sobre la información, su estructura y su-gestión 252 Sobre la información, su estructura y su-gestión 253 Sobre la información, su estructura y su-gestión 254 Sobre la información, su estructura y su-gestión 255 Sobre la información, su estructura y su-gestión 256 Sobre la información, su estructura y su-gestión 257 Sobre la información, su estructura y su-gestión 258 Sobre la información, su estructura y su-gestión 259 Sobre la información, su estructura y su-gestión 260 Sobre la información, su estructura y su-gestión 261 Sobre la información, su estructura y su-gestión 262 Sobre la información, su estructura y su-gestión 263 Sobre la información, su estructura y su-gestión 264 Sobre la información, su estructura y su-gestión 265 Sobre la información, su estructura y su-gestión 266 Sobre la información, su estructura y su-gestión 267 Sobre la información, su estructura y su-gestión 268 Sobre la información, su estructura y su-gestión 269 Sobre la información, su estructura y su-gestión 270 Sobre la información, su estructura y su-gestión 271 Sobre la información, su estructura y su-gestión 272 Sobre la información, su estructura y su-gestión 273 Sobre la información, su estructura y su-gestión 274 Sobre la información, su estructura y su-gestión 275 Sobre la información, su estructura y su-gestión 276 Sobre la información, su estructura y su-gestión 277 Sobre la información, su estructura y su-gestión 278 Sobre la información, su estructura y su-gestión 279 Sobre la información, su estructura y su-gestión 280 Sobre la información, su estructura y su-gestión 281 Sobre la información, su estructura y su-gestión 282