Home UC3M
Home IT
Home / Docencia

Práctica 1: análisis léxico y sintáctico

PFAT


 Objeto de la sesión

El objetivo de esta sesión es desarrollar un analizador léxico y un analizador sintáctico de acuerdo con la especificación que se presenta más adelante. Dichos analizadores recibirán como entrada un fichero de texto que contiene el programa fuente y en el caso de que no haya errores léxicos ni sintácticos producirán como resultado un objeto Java que represente el programa fuente en forma de árbol. Si hay errores léxicos o sintácticos se levantará una excepción.

En este documento se presenta una descripción general del programa fuente que se utilizará en la práctica. Se debe notar que ciertas partes de dicha descripción no se van a utilizar en la práctica 1. En particular, las comprobaciones relativas a los tipos de datos se realizarán en la práctica 2.

Tutoriales

Antes de empezar a realizar la práctica se recomienda 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

Go Up
 Definición del lenguaje de programación a traducir

Descripción general del lenguaje de programación fuente PF2026

Empecemos con un pequeño programa de ejemplo:

pf2026 Ejem1(a,b)

decl
int c;
rational d;

begin
c:= a+b;
d:= a/b;
print(int2str(a) + " + " + int2str(b) + "= " + int2str(c));
print(int2str(a) + " / " + int2str(b) + "= " + q2str(d));
print(int2str(a) + " / " + int2str(b) + "= " + q2str.decimal(d,3));

end;

Un programa en el lenguaje de programación PF2026 consta de las siguientes partes:

  1. Palabra clave pf2026 que indica el inicio del programa.

  2. Un identificador (el nombre del programa).

  3. Opcionalmente, puede declararse una lista de variables de entrada. Estas variables se escriben entre paréntesis y separadas por comas, inmediatamente a continuación del nombre del programa. Estas variables serán siempre de tipo int (el tipo es implícito, es decir, no se indica el tipo en la declaración) y servirán para pasarle datos al programa.

  4. También opcionalmente, pueden declararse variables que serán utilizadas dentro del programa. Las declaraciones de variables están formadas por un tipo de datos seguido de una lista de identificadores (nombres de variables). Los tipos de datos existentes en este lenguaje de programación son int, bool, str y rational.

    Cuando se declara una variable se supondrá que inicialmente tomará el valor false si la variable es de tipo bool, 0 si la variable es de tipo int o rational y "" si la variable es de tipo str.

  5. Cuerpo del programa, que consta de una lista de sentencias. Comienza con la palabra clave begin y termina con end;.

    Las sentencias de un programa pueden ser de los siguientes tipos:

    • Sentencia de asignación: almacena en la variable indicada a la izquierda del := el valor que se obtiene como resultado de evaluar la expresión a la derecha del :=.

    • Bucle while <Exp> do <StamentList> end. La ejecución de esta sentencia comienza evaluando la expresión. Si el resultado es false no se hace nada y se pasa a ejecutar la siguiente sentencia. En caso contrario se ejecuta repetidamente la lista de sentencias contenida dentro del bucle y se vuelve a evaluar la expresión hasta que en una iteración el resultado de evaluar la expresión sea false.

    • Sentencia print. Esta sentencia recibe como argumento un dato de tipo str, que imprime en pantalla, seguido de un salto de línea.

Expresiones en el lenguaje de programación fuente

NOTA: en lo que sigue representaremos los números de tipo rational de la siguiente forma: pe + num/den, donde:

  • pe es un número entero negativo, positivo o cero.
  • num y den son dos números enteros que verifican: a) den > num >=0; b) si num> 0, entonces el máximo común divisor de num y den es 1; y c) si num= 0, entonces den= 1. Puede demostrarse que todo número racional puede descomponerse de una única forma en 3 números enteros que cumplan con lo anterior.

Las expresiones del lenguaje de programación fuente (identificadas en la gramática por el símbolo no terminal <Exp>) deben de ajustarse a lo siguiente.

Expresiones sin tipo definido:

  • Un identificador que sea el nombre de una variable. Será del tipo de la variable.

  • Una expresión entre paréntesis. Será del tipo de la expresión contenida dentro de los paréntesis.

Expresiones de tipo int:

  • Una constante expresada en decimal.

  • Una suma, resta o multiplicación de expresiones de tipo int utilizando los operadores habituales "+", "-", "*".

  • El opuesto de una expresión de tipo int, utilizando el símbolo habitual "-" antes de la expresión.

  • Una expresión que pertenezca al lenguaje definido por la expresión lingüística parte.entera "(" <Exp> ")", donde la subexpresión entre paréntesis ha de ser de tipo rational. El resultado de evaluar esta expresión será el componente pe del dato de tipo rational que se obtendría al evaluar la subexpresión.

  • Una expresión que pertenezca al lenguaje definido por la expresión lingüística numerador "(" <Exp> ")", donde la subexpresión entre paréntesis ha de ser de tipo rational. El resultado de evaluar esta expresión será el componente num del dato de tipo rational que se obtendría al evaluar la subexpresión.

  • Una expresión que pertenezca al lenguaje definido por la expresión lingüística denominador "(" <Exp> ")", donde la subexpresión entre paréntesis ha de ser de tipo rational. El resultado de evaluar esta expresión será el componente den del dato de tipo rational que se obtendría al evaluar la subexpresión.

Expresiones de tipo rational:

  • Una suma, resta o multiplicación cuando al menos uno de los dos operandos sea de tipo rational y el otro sea de tipo rational o de tipo int, utilizando los operadores habituales "+", "-", "*".

  • Una división cuando ambos operandos sean de tipo rational o de tipo int, utilizando el operador habitual "/". Observe que la división de dos operandos de tipo int devuelve un dato de tipo rational.

  • El opuesto de una expresión de tipo rational, utilizando el símbolo habitual "-" antes de la expresión.

Expresiones de tipo str (cadenas de caracteres):

  • Una constante de tipo str.

  • Concatenación de 2 expresiones de tipo str, utilizando el operador "+".

  • Una expresión que pertenezca al lenguaje definido por la expresión lingüística int2str "(" <Exp> ")", donde la subexpresión entre paréntesis ha de ser de tipo int. El resultado de evaluar esta expresión será el objeto de la clase String que se obtendría en Java al invocar el método Integer.toString sobre el dato que se obtendría al evaluar la subexpresión de tipo int.

  • Una expresión que pertenezca al lenguaje definido por la expresión lingüística q2str "(" <Exp> ")", donde la subexpresión entre paréntesis ha de ser de tipo rational. El resultado de evaluar esta expresión será el String "pe + num/den", donde pe, num y den son las 3 componentes del dato de tipo rational obtenido al evaluar la subexpresión. Por ejemplo, el resultado de evaluar q2str(1799/63) será "28 + 5/9".

  • Una expresión que pertenezca al lenguaje definido por la expresión lingüística q2str.decimal "(" <Exp>, c ")", donde la subexpresión entre paréntesis ha de ser de tipo rational y el segundo argumento una constante entera positiva o 0 (una cadena de dígitos). El resultado de evaluar esta expresión será un dato de tipo String que represente el valor obtenido al evaluar la subexpresión de tipo rational con tantas cifras decimales como indique el segundo argumento, sin redondeo. Por ejemplo, el resultado de evaluar q2str.decimal(17/63,3) será "0.269".

Expresiones de tipo bool:

  • Las constantes true y false.

  • La conjunción o disyunción de expresiones lógicas, utilizando los operadores and y or respectivamente.

  • Una expresión lógica negada, utilizando el operador not antes de la expresión lógica.

  • La igualdad entre 2 expresiones utilizando el operador "=". Las dos expresiones comparadas deberán de ser o bien ambas de tipo bool o bien ambas de tipo numérico (int o rational).

  • La comparación entre 2 expresiones de tipo numérico (int o rational) utilizando los operadores "<" (menor que) o ">" (mayor que).

Reglas de precedencia

La precedencia de los operadores es, de menor a mayor:

  1. or
  2. and
  3. not
  4. =
  5. < y >
  6. + y -
  7. * y /
  8. - (opuesto)

Todos los operadores asocian por la izquierda.

Especificación del analizador léxico

El analizador léxico JLex a desarrollar debe reconocer los siguientes tokens:

  • Palabras clave: begin (token BEGIN), and (token AND), or (token OR), not (token NOT), pf2026 (token PROG), decl (token DECL), while (token WHILE), do (token DO), print (token PRINT), int2str (token INT2STR), parte.entera (token PENT), numerador (token NUM), denominador (token DEN), q2str (token Q2STR), q2str.decimal (token Q2STRD) y end (token END).
  • Las palabras clave para los tipos básicos: int, bool, str y rational comparten el mismo token: TYPE.
  • Signos de puntuación y operadores: ";" (token PC), ":=" (token ASIG), "<" (token MENOR), ">" (token MAYOR), "+" (token MAS), "-" (token MENOS), "/" (token DIV), "*" (token MUL), "(" (token PAREN), ")" (token TESIS), "=" (token IGUAL) y "," (token COMA).
  • Identificadores (token IDENT): comienzan por una letra (mayúscula o minúscula), seguida opcionalmente de cualquier cadena de letras y dígitos.
  • Constantes, que pueden ser de tres tipos:
    • Números enteros no negativos (token CINT), es decir, cualquier cadena de dígitos. Ejemplos: 1, 0, 2010
    • Las constantes lógicas true y false (token CLOG)
    • Constantes de tipo str (cadenas de caracteres, token CSTR). Necesariamente tienen que comenzar y terminar con el carácter comilla (") y entre las dos comillas puede haber cualquier cadena de caracteres que no contenga el símbolo comilla. Adicionalmente, puede haber entre las dos comillas que delimitan la constante una o varias comillas siempre y cada ocurrencia del carácter comilla vaya inmediatamente precedida del carácter \. Ejemplos: "Hola Mundo", "Ejemplo con \" comillas \"".

El lenguaje distingue mayúsculas y minúsculas; por lo tanto mientras begin es una palabra clave, Begin es un identificador y BEGIN 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 (además debe definir al final del fichero Yylex las reglas para consumir los espacios en blanco y para dar error léxico cuando no se encuentre ningún patrón válido). 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:

  1. Para cada token definir la expresión regular asociada, según el formato requerido por JLex (véase la documentación que se proporciona).
  2. A continuación de la expresión regular definir la acción que se toma cuando se reconoce dicho token. Para los tokens que no tienen ningún dato asociado, escribiremos {return tok(sym.X, null); }, donde X es el nombre que se le ha dado al token en cuestión en el fichero CUP.

    Para los tokens que tienen un dato asociado, escribiremos {return tok(sym.X, obj); }, donde X es el nombre que se le ha dado al token en cuestión en el fichero CUP y obj es el objeto Java asociado al token. Para construir el objeto Java asociado a un token puede utilizar el método yytext(), tal y como se explica en la documentación de JLex.

    Como se explica en el manual de CUP y se ve en clase, un fichero CUP declara una lista de tokens, donde cada token es representado por un idendificador (por ejemplo, AND). Cuando se genera el analizador sintáctico a partir del fichero CUP, se producen como resultado 2 ficheros Java. Uno de ellos, que lleva por nombre sym.java contiene una clase llamada sym en la que simplemente se asocia un número entero a cada uno de los tokens que se han declarado en el fichero CUP. Por lo tanto, sym.X es el entero que utiliza la clase sym para identificar al token X.

  3. En el caso de los espacios en blanco, saltos de línea, etc., escribiremos como acción { }, cuyo efecto es no devolver nada y seguir consumiendo caracteres de entrada para producir el siguiente token.

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.

La gramática que define la sintaxis del lenguaje de programación fuente es la siguiente:

G=<ET, EN, S, P>
ET= {COMA, PC, PAREN, TESIS, BEGIN, END, ASIG, AND, OR, NOT, 
    IF, THEN, ELSE, PROG, DECL, WHILE, DO, PRINT,  
    MAS, MENOS, MUL, Q2STR, Q2STRD, PENT, NUM, DEN, 
    DIV, MAYOR, MENOR, IGUAL, INT2STR, IDENT, CSTR,
    CLOG, CINT, TYPE}

EN= {S, <IdentList>, <StatementList>, <Body>, <Statement>,
    <Exp>, <LDecl>, <Decl>}

S= S

P= {
S->  PROG IDENT PAREN <IdentList> TESIS DECL <LDecl> <Body> 
   | PROG IDENT DECL <LDecl> <Body> 
   | PROG IDENT PAREN <IdentList> TESIS <Body> 
   | PROG IDENT <Body> 

<LDecl>-> <Decl> PC
       | <Decl> PC <LDecl>

<Decl>-> TYPE <IdentList>

<IdentList>-> IDENT
           | <IdentList> COMA <IdentList>

<Body>-> BEGIN <StatementList> END PC

<StatementList>-> <Statement> PC 
               | <Statement> PC <StatementList>

<Statement>-> IDENT ASIG <Exp> 
           | PRINT PAREN <Exp> TESIS 
	   | WHILE <Exp> DO <StatementList> END

<Exp>-> <Exp> AND <Exp>
         | <Exp> OR <Exp>
         | NOT <Exp>
         | PAREN <Exp> TESIS
         | IDENT
         | CLOG
         | <Exp> MAS <Exp>
         | <Exp> MENOS <Exp>
         | MENOS <Exp>
         | <Exp> MUL <Exp>
         | <Exp> DIV <Exp>
         | <Exp> MAYOR <Exp>
         | <Exp> IGUAL <Exp>
         | <Exp> MENOR <Exp>
         | PENT PAREN <Exp> TESIS
         | NUM PAREN <Exp> TESIS
         | DEN PAREN <Exp> TESIS
         | Q2STR PAREN <Exp> TESIS
         | Q2STRD PAREN <Exp> COMA CINT TESIS
         | CINT
         | INT2STR PAREN <Exp> TESIS
         | CSTR
}
Go Up
 Requisitos que deben cumplir los ficheros JLex y CUP

Para la prueba del analizador léxico y el analizador sintáctico, se utilizara la clase Main que se proporciona. La ejecución de dicha clase se realizará de acuerdo con lo siguiente:

java Main <nombre_fichero>

Donde <nombre_fichero> es el nombre del fichero (con el path si es necesario) del programa fuente a analizar.

El programa deberá de levantar una excepción que no deberá de ser capturada en el caso de que el programa fuente tenga algún error de sintaxis. Los mensajes emitidos por excepciones producidas por errores en la fase de análisis sintáctico deberán indicar una línea del programa orientativa del lugar en el que se ha producido el error.

Asímismo, el programa deberá de levantar una excepción de tipo LexerException, que no deberá de ser capturada en el caso de que el programa fuente tenga algún error léxico.

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.

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:

  1. Clases del paquete Errors.

  2. Clases del paquete AST que usted debe desarrollar.

  3. Clases parser y sym generadas por CUP.

  4. Clase Yylex generada por JLex.

  5. Clase Main que se proporciona.

Aquellas prácticas que no se puedan compilar en este orden serán calificadas con 0.

Go Up
 Ayudas y sugerencias

Se proporciona un juego de tests con el que probar la práctica. De entre ellos solamente deberían de producir un error de sintaxis los tests en carpetas cuyo nombre comienza por ErrSint y error léxico los tests en carpetas cuyo nombre comienza por ErrLex. Puede ocurrir que alguno de los ejemplos ErrLex de error de sintaxis (y no léxico).

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.

Go Up
 Ficheros a entregar

Se deberán entregar exclusivamente los siguientes ficheros:

  • fichero Yylex en formato JLex para el analizador léxico

  • fichero parser en formato CUP para el analizador sintáctico

  • fichero AST.zip, que contiene exclusivamente un directorio de nombre AST que a su vez contiene exclusivamente los ficheros .java que usted ha desarrollado para modelar árboles de sintaxis abstracta.

Go Up

Localización | Personal | Docencia | Investigación | Novedades | Intranet
inicio | mapa del web | contacta

Last Revision: