Compilador de Pascal : Fase 1

Anuncio
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.
Descargar