|
Práctica Obligatoria |
![]() |
|
El objetivo de esta práctica es desarrollar un compilador sencillo, implementando todos sus componentes: analizador léxico, analizador sintáctico, analizador semántico y generación de código. El compilador que tiene que desarrollar debe de traducir programas escritos en el lenguaje fuente que se describe más adelante a Java. El compilador recibirá como entrada un fichero de texto con el programa fuente a traducir y producirá como resultado una o varias clases Java (siempre y cuando el programa fuente no contenga errores). Si el programa fuente contiene errores se levantará una excepción. Tutoriales Antes de empezar a realizar la práctica se recomienda a los alumnos realizar está práctica para familiarizarse con el entorno de trabajo, así como consultar los manuales de CUP y JLex disponibles a través de Aula Global o bien estos pequeños resúmenes de CUP y JLex |
|
Empecemos con un pequeño programa de ejemplo: PF2011 Ejemplo(argumento) vars int x, y; { x:=Str2Int(argumento); y:=0; while(x>0) { y:=y+x; x:=x-1; } print("Resultado= " + Int2Str(y)); } Un programa en el lenguaje de programación fuente consta de las siguientes partes:
Las sentencias de un programa pueden ser de los siguientes tipos:
|
|
El analizador léxico JLex a desarrollar debe reconocer los siguientes tokens:
El lenguaje distingue mayúsculas y minúsculas; por lo tanto mientras print es una palabra clave, Print es un identificador y PRINT es otro identificador distinto del anterior. Recuerde que también tiene que especificar en el fichero JLex que se deben reconocer y consumir (sin generar token) los espacios en blanco, tabuladores, saltos de línea y retornos de carro. Para desarrollar el analizador léxico pedido lo único que hay que hacer es completar este esqueleto de fichero JLex en el que lo único que falta es definir para cada token a reconocer, la expresión regular asociada y la acción asociada. A modo de ejemplo, se proporciona ya definido el token para la palabra clave and. Como puede observarse en la definición de la regla para la palabra clave and, lo que hay que hacer es:
El analizador léxico deberá lanzar una excepción de tipo LexerException, que se proporciona, cuando se detecte un error léxico. |
|
Definición de la sintaxis del lenguaje de programación fuente A continuación vamos a dar la definición de la sintaxis del lenguaje fuente por medio de una gramática independiente del contexto. Como excepción, la sintaxis de las expresiones la daremos a continuación en leguaje natural. Los tokens generados por el analizador léxico se corresponden con los símbolos terminales de la gramática que define el analizador sintáctico. La gramática que define la sintaxis del lenguaje de programación fuente es la siguiente:
NOTA: no se proporcionan, intencionadamente, reglas de producción que tengan a <Sent> como antecedente. Deberá definirlas usted (puede ser necesario añadir algún símbolo no terminal auxiliar). <Sent> representa una sentencia o bien una lista de una o más sentencias entre llaves. Expresiones en el lenguaje de programación fuente Las expresiones del lenguaje de programación fuente (identificadas en la gramática con el símbolo no terminal <Exp>) deben ajustarse a lo siguiente: Expresiones sin tipo definido:
Expresiones de tipo int:
Expresiones de tipo string:
Expresiones de tipo bool:
Reglas de precedencia La precedencia de los operadores es, de menor a mayor:
Todos los operadores asocian por la derecha. |
|
Tal y como se realiza en un compilador habitualmente, en la fase de análisis semántico se debe comprobar que el programa fuente se ajusta a todas las especificaciones del lenguaje de programación fuente que no hayan podido ser comprobadas en la fase de análisis sintáctico. A estos efectos, se debe entender que la descripción del lenguaje fuente realizada anteriormente forma parte de dichas especificaciones, y por lo tanto, el analizador semántico deberá comprobar que el programa fuente se ajusta a todo lo indicado y que no se haya verificado en la fase de análisis sintáctico. Esto aplica particularmente a las comprobaciones de tipos de las expresiones. Las expresiones que combinan una o 2 subexpresiones se deberán ajustar a lo indicado anteriormente. Por ejemplo, de acuerdo con lo indicado anteriormente, una expresión que combine una subexpresión de tipo int con otra subexpresión de tipo string por medio del operador '+' debería producir un error de tipo, detectado en la fase de análisis semántico. Además el analizador semántico deberá comprobar lo siguiente:
El analizador semántico debe lanzar una excepción, que no debe ser capturada, cuando se detecte un error semántico. La clase o clases utilizadas para las excepciones correspondientes a errores semánticos se pueden definir libremente, pero deben extender la clase CompilerExc, incluida en el fichero Errors.zip. Se valorará que los mensajes que se generen identiifiquen claramente el error producido. |
|
Comportamiento esperado de la ejecución de un programa fuente La ejecución de un programa fuente consiste en la ejecución de la secuencia de instrucciones que lo forman. El comportamiento de las instrucciones de asignación, condicional (if...then) y bucle (while...) es análogo al de sus equivalentes en lenguaje Java. El comportamiento de la sentencia break se explicó anteriormente. El comportamiento de la sentencia print es equivalente al del método System.out.println de Java. El comportamiento de las expresiones se explicó anteriormente. Se supone que las variables al declararse (incluyendo, si los hubiera, los argumentos del programa) se inicializan por defecto a los siguientes valores:
|
|
El código Java generado por el compilador deberá cumplir lo siguiente:
|
|
Para esta práctica se deberá desarrollar una clase Main. La ejecución de dicha clase se realizará de acuerdo con lo siguiente: java Main <nombre_fichero> <nombre_clase_generada> Donde <nombre_fichero> es el nombre del fichero (con el path si es necesario) del programa fuente a traducir y <nombre_clase_generada> es el nombre de la clase Java generada al ejecutar el compilador que contiene su propio método main. El programa deberá de levantar una excepción que no deberá ser capturada en el caso de que el programa fuente tenga algún error léxico, sintáctico o semántico. Si el programa fuente no contiene errores, el compilador debe depositar en el directorio actual uno o varios ficheros que contendrán la o las clases Java generadas para el programa fuente. Obligatoriamente las clases generadas por CUP deberán pertenecer a un paquete llamado Parser y la clase generada por JLex deberá pertenecer a un paquete llamado Lexer. Las clases Java que desarrolle en esta práctica obligatoriamente deberán estar organizadas en los siguientes paquetes:
La clase Main no pertenecerá a ningún paquete. Orden de compilación Antes de entregar la práctica deberá cerciorarse de que su práctica puede compilarse correctamente con javac siguiendo el siguiente orden:
Aquellas prácticas que no se puedan compilar en este orden serán calificadas con 0. |
|
Se proporciona un juego de tests con el que probar la práctica. De entre ellos solamente deberían de producir un error los tests en carpetas cuyo nombre comienza por ErrSem, ErrSint y ErrLex. Puede ocurrir que alguno de los ejemplos ErrLex de error de sintaxis (y no léxico). Los tests de las carpetas cuyo nombre comienza por ErrSem deberían producir un error semántico. Las carpetas que contienen programas fuente que no tienen errores (es decir, carpetas cuyo nombre empieza por Ejem) contienen además un fichero de nombre ejecucion.txt que contiene el resultado esperado de una o varias ejecuciones (con diferentes argumentos) de la o las clases Java generadas para el programa fuente contenido en la carpeta. Se advierte que se realizarán tests adicionales a los proporcionados a las prácticas recibidas, por lo que se recomienda a los alumnos que planifiquen tests complementarios a su práctica. |
|
Se deberán entregar exclusivamente los siguientes ficheros:
|
|