Compilador de Pascal : Fase 1 1. Introducción Nos disponemos a crear un compilador del lenguaje de programación Pascal levemente modificado. Las fases por las que pasa un compilador para compilar el código fuente son, a grandes rasgos, la de análisis léxico, análisis sintáctico, análisis semántico, generación de código intermedio y generación de código final. En esta fase del proceso se abarca el diseño y la implementación del analizador léxico y de varios elementos auxiliares necesarios hasta este punto como la tabla de símbolos y el gestor de errores. La implementación se ha realizado en Java, y para la parte referente al analizador léxico se ha utilizado la herramienta JFlex. 2. El lenguaje Pascal Basándonos en la gramática de Pascal de Kathleen Jensen y Niklaus Wirth, www.cs.utexas.edu/users/novak/grammar.html, la hemos ampliado para facilitar la comprensión y el proceso de derivación. (operador, puntuación…), y hemos incluido dos nuevas reglas: String y Char. Estas son las reglas de la gramática: <operador>::= <op_mat> | <op_comp> | <op_log> | := | ^ <op_mat> ::= + | - | * | / | div | mod <op_comp>::= < | > | = | <> | <= | >= <op_log> ::= and | or | xor | neg | shr| shl <punt> ::= ; | . | , | : | ( | ) | [ | ] | { | } <delim> ::= ‘ ‘(blanco)|EOF|TAB| EOL| + | - | * | / | > | < |= |^ |<punt>| <num> ::= <digito>>digito>*[.<digito><digito>*][(e|E)[-|+] <digito><digito>*]]| <digito> ::= 0 | 1 | 2 | … | 9 Incluida por completitud <ID> ::= <letra>(<letra>|<dígito>|_)* <letra> ::= a | … | z | A | … | Z Incluida por completitud <num> ::= <digito>>digito>*[.<digito><digito>*][(e|E)[-|+] <digito><digito>*] | $(A | … | F |<digito>)* <string> ::= “<carácter>*” <char> ::= ‘<carácter>’ | #<digito>* <carácter> :: = Cualquier carácter del código ASCII, excepto la comilla simple (Incluido por completitud) <pal_res> ::=Palabras reservadas de Pascal 3. Diseño e implementaciones 3.1 Diseño general del compilador Cada ejecución del compilador tendrá una instancia de objeto de cada módulo del análisis además de uno del gestor de errores y de otro de la tabla de símbolos. Todos ellos serán controlados en la clase “Control”, que hará de intermediaria entre los módulos del compilador. De esta manera centralizamos todo el control, además de ser la clase que inicia a todos los demás módulos. 3.2 Diseño e implementación del analizador léxico El analizador léxico va leyendo el fichero fuente y generando los tokens que va reconociendo para pasárselos a la clase de control, que posteriormente se los dará al analizador sintáctico. Se encarga también de eliminar del código los comentarios y los espacios, tabuladores, retornos de carro, etc. Un token es una estructura que representa para cada elemento identificado del fichero fuente toda la información relevante y necesaria para el resto del proceso. En nuestro caso, la hemos codificado como un objeto con 3 parámetros: el tipo(un String), la línea en la que fue encontrado(un entero) y un objeto en el que guardamos otros atributos importantes, como en el caso de identificadores el puntero a la tabla de símbolos donde se encuentra. Los tokens que reconoce el autómata que implementa la clase léxico son: CHAR: Bien lo introduzcamos como ‘carácter’ ó como $(número entero), devolviendo un error si el número es mayor de 127, ya que no corresponde a ningún carácter ASCII. En el atributo llevaremos el carácter que ha sido reconocido. IDENTIFICADOR: en el atributo añadiremos un puntero a la Tabla de Símbolos que indica la posición que ocupa dentro de la misma. INTEGER: En el atributo llevaremos el valor del número entero que hayamos reconocido. También se reconoce si se introduce en formato hexadecimal, conservando en el atributo el valor equivalente de la conversión. Es decir, si hemos reconocido $FF, entonces el valor devuelto en el atributo es 256. REAL: En el atributo llevaremos el valor del número real que hayamos reconocido. PUNT: Engloba a los símbolos básicos de puntuación: {; : , .}. En este caso no incluimos nada en el atributo. OP_COMP: Engloba los operadores expresados en la regla <op_comp> de nuestra gramática. Su atributo es la cadena con el operador que se haya reconocido. OP_LOG: Engloba los operadores expresados en la regla <op_log> de nuestra gramática. Su atributo es la cadena con el operador que se haya reconocido. OP_MAT: Engloba los operadores expresados en la regla <op_mat> de nuestra gramática. Su atributo es la cadena con el operador que se haya reconocido. OP_PUNT: Indica que hemos dado con un puntero. No lleva atributos. OP_ASIG: Indica que hemos dado con una operación de asignación. No lleva atributos. STRING: Indica que hemos dado con un String. En el atributo llevaremos el contenido del String que hemos encontrado. ABRE_PAREN: Indica que hemos dado con paréntesis de apertura. No lleva atributos. CIERRA_PAREN: Indica que hemos dado con paréntesis de cierre. No lleva atributos. ABRE_CORCHETE: Indica que hemos dado con corchete de apertura. No lleva atributos. CIERRA_CORCHETE: Indica que hemos dado con corchete. No lleva atributos. PAL_RES: Indica que hemos dado con una palabra reservada. Las palabras reservadas que reconocemos son: "ARRAY"|"array"|"BEGIN"|"begin"|"CASE"|"case"|"CONST"|"const"|"DIV"| "div"|"DO"|"do"|"DOWNTO"|"downto"|"ELSE"|"else"|"END"|"end"|"FILE"|"file"| "FOR"|"for"|"FORWARD"|"forward"|"FUNCTION"|"function"|"GOTO"|"goto"|"IF"| "if"|"IN"|"in"|"LABEL"|"label"|"MOD"|"mod"|"NIL"|"nil"|"NOT"|"not"|"OF"|"of"| "PACKED"|"packed"|"PROCEDURE"|"procedure"|"PROGRAM"|"program"| "RECORD" |"record"|"REPEAT"|"repeat"|"SET"|"set"| "THEN"|"then" |"TO"| "to" |"TYPE"|"type"|"UNTIL"|"until"|"VAR"|"var"|"WHILE"|"while"|"WITH"|"with" El analizador léxico emite un token con cada invocación al método “siguiente_token()”.Esta llamada la realizará la clase de control que como dijimos servía para mediar entre todos los módulos. Para lo único que no interviene el control es para gestionar la relación entre el léxico y la tabla de símbolos: cada vez que el léxico se encuentre un identificador, mandará a la tabla de símbolos que lo inserte sin preocuparse de nada más. Será la propia tabla de símbolos la encargada de insertarlo en el ámbito actual en caso de no estar repetido y si está en el bloque de declaración de variables. En caso de encontrar una palabra reservada, ordena a la tabla de símbolos que lo meta en la tabla auxiliar. 3.2a Errores Léxicos: Hemos reconocido 4 errores léxicos principalmente: 1-Identificador mal formado: cuando encontramos un identificador con un número delante. Ej: 1auxiliar 2-Carácter mal formado: cuando usamos la notación para meter código ASCII y como número uno mayor que 127,(el rango va de 0 a 127). También falla si utilizamos la comilla simple y en vez de un carácter metemos una cadena. Ej: $135 ‘caracternovalido’ 3-Carácter ilegal: todo aquello que no ha encajado con alguna de las reglas de la gramática y no sea alguno de los demás errores. 4-Real mal formado: : cuando hemos formado un real de manera incompleta. Ej: 3.e23 Cuando se encuentra alguno de estos errores, se lanza una excepción que deberá ser capturada por el módulo de control para meter un nuevo error en el gestor de errores. No se interrumpe la ejecución del programa, que seguirá leyendo el fichero fuente hasta que encuentre el final, reconociendo todos los nuevos tokens que encuentre a su paso. La excepción que se lanza es de tipo ExcepcionLexico. En ella van almacenados la línea en la que se produjo el error y un entero que lleva el tipo de error (1..4). 3.3 Diseño e implementación del gestor de errores La clase GestorErrores implementa el gestor de errores. Posee un atributo de tipo HashMap cuyo contenido es un ArrayList de códigos de error(de 1 a 4 por ahora), y cuya clave es un entero que identifica la línea para la que se guardan los errores que contenía. De esta manera conseguimos que se lleven los errores en órden: no tenemos más que pedirle a nuestro gestor los errores que se han producido en una línea y nos devolverá un ArrayList con los códigos de dichos errores,(en caso de que hubiera, claro). El segundo atributo de la clase es un ArrayList de String que contiene para cada tipo de error la descripción que será mostrada cuando se listen los errores al usuario final del compilador. Como hemos adelantado en el ejemplo, la interfaz del gestor de errores posee dos métodos, uno de inserción de error y otro de consulta de todos los ya almacenados: insertaError(int clave,int tipo) HashMap<Integer,ArrayList> dameTabla() De esta manera, aunque no conozcamos por ahora el número de tipos de error que va tener nuestra aplicación, resulta relativamente fácil introducir nuevos sin tener que modificar gran cosa. 3.4 Diseño e implementación de la tabla de símbolos La tabla de símbolos es la estructura en donde vamos a almacenar toda la información importante del compilador. La hemos estructurado en 3 partes: La más elemental es la clase Símbolo: en ella se almacenan todos los parámetros importantes que pueda tener un elemento de la tabla de símbolos: tipo, línea, tamaño, parámetros, referencias a ámbitos, etc. Tiene asimismo accesoras y mutadoras de todos estos parámetros, pues cuando se crea un nuevo Símbolo no se sabe el valor de todos sus atributos. Le sigue la clase TS, que es donde vamos a almacenar los símbolos que vayamos creando. Posee un HashMap que asocia a cada lexema que se quiera introducir,(de tipo String), un Símbolo nuevo. También posee una referencia a la tabla que le apunta, para poder movernos de una tabla a otra fácilmente. Por último lleva un booleano que indica si está habilitada la escritura para la tabla o no. Aparte de las accesoras, posee métodos para insertar un lexema y obtener contenidos del mismo. Por último, en un nivel superior, tenemos la clase tablaSimbolosGlobal, que 4 atributos: una tabla de Símbolos actual, que indica en qué ámbito estamos actualmente, una tabla de símbolos inicial, que nos da la tabla más “exterior”, en el bloque principal del programa, y una tabla de palabras reservadas, que contiene todas las palabras reservadas que hemos ido encontrando hasta el momento. Posee métodos para insertar un nuevo lexema en alguna de las tablas, abrir y cerrar ámbitos, así como las accesoras de todos sus atributos.