jlex y cup2

12
Tutorial Jlex Y Java Cup Tutorial Jlex y Java Cup http://openfecks.wordpress.com/  por Josué Ortega http://openfecks.wordpress.com/

Upload: alfonso-flores-leal

Post on 02-Oct-2014

514 views

Category:

Documents


4 download

TRANSCRIPT

Page 1: Jlex y Cup2

Tutorial Jlex Y Java Cup

Tutorial Jlex y Java Cuphttp://openfecks.wordpress.com/ 

por Josué Ortega

http://openfecks.wordpress.com/

Page 2: Jlex y Cup2

Tutorial Jlex Y Java Cup

JLex y Java CUP

JLEX

Jlex no es más que un generador de un analizador léxico  parecido a LEX, el cual toma una cadena como entrada una cadena de caracteres, y lo convierte en una secuencia de tokens.

CUP

Cup es un generador de analizadores sintácticos LALR  en Java el cual recibe de entrada un archivo con la estructura de la gramática y su salida es un parser escrito en Java listo para usarse.

Decidí dividir el tutorial en varias secciones para hacer más fácil el aprendizaje de estas herramientas.

INDEX• Estructura de un Archivo Jlex• Estructura de un archivo Cup• Integración Jlex con Cup• Compilacion y Ejecucion

Estructura Archivo JLex

Estructura del Archivo Jlex

Un archivo de entrada Jlex, es un archivo plano con la siguiente estructura:

codigo del usuario%%Directivas Jlex%%Reglas para las Expresiones Regulares

Código del Usuario

Es la parte del archivo de entrada donde se coloca el codigo java que deseamos usar en la clase que será generada, esto quiere decir que Jlex copiará directamente el codigo a la clase generada, aqui deben ir los importes a otras librerias. TODO ESTO ANTES DE LOS PRIMEROS (%%).

http://openfecks.wordpress.com/

Page 3: Jlex y Cup2

Tutorial Jlex Y Java Cup

Directivas JLex

En esta seccíon irán las directivas, o especificaciones para que opere JLEX, para obtener la salida deseada.

Reglas para las Expresiones Regulares

En esta seccion del archivo Jlex, es donde se definen las reglas para obtener los tokens de la cadena que se esta leyendo.

Con un ejemplo explicare mejor cada una de estas secciones.

Para el ejemplo escribí un programa que reconoce las siguientes palabras reservadas:

int, string, if, then, else, for, while

Reconoce identificadores, y enteros.

El codigo para hacer Jlex para genera el analizador lexico del ejemplo es el siguiente:

/*AQUI PUEDEN IR LOS IMPORTS */%%%{/*CODIGO USUARIO*//*Pequeña funcion para imprimir en pantalla*/

public void imprime(String foo){System.out.println(foo)}%}/*DIRECTIVAS JLEX*/%class Yylex%public%full%char%line%cup%eofval{System.out.println("FIN DEL ARCHIVO");%eofval}entero=[0­9]Id=[a­zA­Z][a­zA­Z0­9]*%%/* MANEJO DE LAS PALABRAS RESERVADAS*/"while"  {imprime("while");}

http://openfecks.wordpress.com/

Page 4: Jlex y Cup2

Tutorial Jlex Y Java Cup

"int"   {imprime("int");}"if" {imprime("if");}"then" {imprime("then");}"for"{imprime("for");}/*expresion regular para un entero, tomando el conjunto definiddoanteriormente como entero*//*un entero 1 o mas veces*/({entero})+  {imprime("entero"+}{Id} {imprime("Identificador");}/*con la siguiente linesa ignoramos los espacios en blanco*/(" ") {System.out.println("espacio");}/*con esta ignoramos los saltos de linea, tabulaciones,*/[\t\r\n\f] {}/*error lexico:*/. {System.out.println("error");}

}/*error lexico:*/. {System.out.println("error");}

El código escrito dentro de los corchetes es el el código que queremos que se ejecute cada vez que el scanner encuentra los tokens a su izquierda. Este código queda sin modificar a la hora de generar el archivo de salida java

Estructura de Archivo Cup

A continuación detallaré como se estructura un archivo de entrada para Cup.

Básicamente un archivo para Cup tiene la siguiente estructura:

< imports java >< codigo del usuario para el parser><codigo del usuario para las acciones de la gramatica>< Declaracion de Variables para la gramatica><Gramatica>Imports:  En esta sección creo que no tengo que ampliar mucho desde que programamos Java sabemos como son los imports de librerias.Código del Usuario para el Parser:  Como el código Java es generado por la herramienta es muy difícil modificar lo en el archivo de salida. Así que aquí podemos declarar métodos y variables que pensamos usar en la clase resultante. Si se declaran variables o métodos públicos en esta sección estos podran ser accedidos por otras clases.

Se declara:

parser code {: /* Codigo del parser*/:}Código del Usuario para las Acciones de la Gramática:  Como nuestro propósito es el de generar un Compilador con estas 

http://openfecks.wordpress.com/

Page 5: Jlex y Cup2

Tutorial Jlex Y Java Cup

herramientas o un interprete, necesitamos generar una salida ya sea esta errores semánticos, sintácticos o traducción a un código equivalente, para esto tenemos que hacer uso de traducciones dirigidas  por sintaxis.

Sería muy engorroso programar largas funciones en cada acción del gramática  así que estas las podemos declarar en esta sección  y solo mandarlas a llamar en cada acción gramatical.

Se declara de la siguiente manera:

action code {:/*Codigo para las acciones*/:}Declaración de Variables para la Gramática :  En esta sección toca declarar las variables que se utilizaran en la gramática, estas variables pueden ser de dos tipos:

• Variables Terminales < terminal>• Variables No Terminales <non terminal>

Las variables terminales serán todos los símbolos terminales de la gramática y las variables No­Terminales serán todas las variables que representaran producciones.

La sintaxis para la declaración es la siguiente:

<tipo de variable> < tipo de dato >  < id de la variable >

Donde <tipo de variable > puede ser Terminal o No terminal 

<tipo de dato> puede ser cualquier tipo de dato primitivo de Java o uno creado por nosotros mismos. Si se no se especifica el tipo de dato Cup lo trabajará como un tipo de dato Symbol.

<id de la variable > aquí se especifica el id de la variable, se puede usar una lista de identificadores separadas por coma si deseamos variables del mismo tipo.

 Gramática: En esta sección del archivo es donde escribiremos nuestra gramatica. La gramatica tiene la siguiente sintaxis :<non terminal > ::= < terminales o No terminales >  ;

Como un no terminal puede tener mas de un lado derecho en Cup se utiliza el simbolo “|”

<non terminal > ::= < terminales o No terminales >                   |<terminales o No terminales>  ;

Como es esperado se pueden escribir muchas producciones.

Ejemplo  de un archivo cup para una Expresion Booleana: 

/*Por el momento dejaremos el action code y parser code vacios esto se

http://openfecks.wordpress.com/

Page 6: Jlex y Cup2

Tutorial Jlex Y Java Cup

explicara mas a detalle en otra seccion del tutorial*/action code{::}parser code{::}

/*Declaracion de variables no terminales*/non terminal COND, OREXP,ANDEXP,IGEXP,CMP,SIMBOLOSCOMPARAR,TIPO_DATO;/*DECLARACION DE VARIABLES TERMINALES */terminal or_,and_,igual_igual,no_igual,mayor, menor, mayor_igual,menor_igual,open_par,close_par,id,numero,true,false;Start with COND; // start with sirve para indicarle al parser con que produccion empezarCOND::=OREXP;OREXP::=OREXP or_ ANDEXP    |ANDEXP;

ANDEXP::=ANDEXP and_ IGEXP      |IGEXP;

IGEXP::= IGEXP igual_igual CMP      |IGEXP no_igual CMP      |CMP;

SIMBOLOS_COMPARAR::=mayor                |menor                |mayor_igual                |menor_igual;

CMP::= CMP SIMBOLOS_COMPARAR TIPO_DATO      |TIPO_DATO      |open_par COND close_par ;TIPO_DATO::= id          |numero          |true          |false;

Integración Jlex con Cup

Ya que sabemos como hacer archivos de entrada para Jlex y Cup ahora es hora de hacer que funcionen en conjunto. Para hacer de mas ilustrativo el ejemplo usaremos una clase externa al scanner y al parser que nos servirá para almacenar información de cada token que se esta leyendo. Llamaremos a esta clase token

class token(){int posicionX;int posicionY;String valor;

http://openfecks.wordpress.com/

Page 7: Jlex y Cup2

Tutorial Jlex Y Java Cup

public token(String val,int x,int y){   this.valor=val;   this.posicionX=x;   this.posicionY=y;}    public int getX(){return this.posicionX;}    public int getY(){ return this.posicionY;}    public String getValor(){return this.valor;}}

Seguiremos con el ejemplo de la expresión condicional , para esto debemos escribir el archivo jlex para que reconozca las palabras reservadas ó terminales del lenguaje. El archivo quedaría de la siguiente forma:

import java_cup.runtime.Symbol;%%%{  public void imprime(String str){    System.out.println(str+"­"+yychar+"­"+yyline);  }%}%class lexc%public%char%line%ignorecase%cup%full%type java_cup.runtime.Symbol%implements java_cup.runtime.Scanner%eofval{System.out.println("FIN DEL ARCHIVO");return null;%eofval}letra=[a­zA­Z]entero=[0­9]id=[a­zA­Z][A­Za­z0­9]*%% "(" {imprime("Abre Parentesis");      return new Symbol(csym.open_par,new token(yytext(),yychar,yyline));      }")" {imprime("Cierra Parentesis");      return new Symbol(csym.close_par,new token(yytext(),yychar,yyline));

http://openfecks.wordpress.com/

Page 8: Jlex y Cup2

Tutorial Jlex Y Java Cup

"true" {          imprime("true");          return new Symbol(csym.true_,new token(yytext(),yychar,yyline));      }"false" {          imprime("false");          return new Symbol(csym.false_,new token(yytext(),yychar,yyline));        }"<=" {imprime("menor igual");      return new Symbol(csym.menor_igual,new token(yytext(),yychar,yyline));    }">=" {imprime("mayor igual");      return new Symbol(csym.mayor_igual,new token(yytext(),yychar,yyline));      }"||" {imprime("or");        return new Symbol(csym.or_,new token(yytext(),yychar,yyline));      }"&&" {imprime("and");        return new Symbol(csym.and_,new token(yytext(),yychar,yyline));      }"==" {imprime("igual_igual");        return new Symbol(csym.igual_igual,new token(yytext(),yychar,yyline));      }"!=" {imprime("no igual");        return new Symbol(csym.no_igual,new token(yytext(),yychar,yyline));      }({id})+("_")*({id})* {imprime("id");        return new Symbol(csym.id,new token(yytext(),yychar,yyline));        }{entero}+ {imprime("entero");          return new Symbol(csym.entero,new token(yytext(),yychar,yyline));        }[\t\r\f]  {}[\n] {yychar=0;}" " {}

http://openfecks.wordpress.com/

Page 9: Jlex y Cup2

Tutorial Jlex Y Java Cup

 . {imprime("error: "+yytext());    }

Explicaré el fragmento de código que se utilizó al lado derecho:

")" {imprime("Cierra Parentesis");      return new Symbol(csym.close_par,new token(yytext(),yychar,yyline));}

Se llama la función imprime que se definió al inicio del archivo. Ahora bien, el scanner es construido de tal manera que dentro de el existe una funcion con una sentencia de control donde se decide que tipo de token se esta leyendo y que valor retornar. Allí reside el hecho de escribir el return. Como podemos ver se retorna un tipo de dato Symbol, este en su constructor recibe dos parámetros :

• El primero que es un entero que es declarado en la clase sym generada por cup (Explicaré mas adelante)

• El segundo que recibe es de tipo Object, esto nos facilita poder enviar cualquier tipo de dato que deseemos., en este caso fue un tipo token, el que hemos definido al inicio de esta sección

.Una vez terminado nuestro archivo jlex es hora de escribir el archivo cup:

action code{: public void ImprimeValor(String str){  System.out.println("el valor del token"+str) ;   }:}parser code{:public void syntax_error(Symbol st){    token t=(token)st.value;    report_error("Error Sintactico:"+ t.getValue()+"­ "+t.getX()+"­"+t.getY(),null);:}

/*Declaracion de variables no terminales*/non terminal token COND, OREXP,ANDEXP,IGEXP,CMP,SIMBOLOSCOMPARAR,TIPO_DATO;/*DECLARACION DE VARIABLES TERMINALES */terminal token or_,and_,igual_igual,no_igual,mayor, menor, mayor_igual,menor_igual,open_par,close_par,id,numero,true,false;Start with COND; // start with sirve para indicarle al parser con que produccion empezarCOND::=OREXP;OREXP::=OREXP or_ ANDEXP

http://openfecks.wordpress.com/

Page 10: Jlex y Cup2

Tutorial Jlex Y Java Cup

    |ANDEXP;

ANDEXP::=ANDEXP and_ IGEXP      |IGEXP;

IGEXP::= IGEXP igual_igual CMP      |IGEXP no_igual CMP      |CMP;

SIMBOLOS_COMPARAR::=mayor:m{:RESULT=m:}                |menor:m{:RESULT=m:}                |mayor_igual:m{:RESULT=m:}                |menor_igual:m{:RESULT=m:};

CMP::= CMP:c SIMBOLOS_COMPARAR:sc TIPO_DATO:t{:                String val1=c.getValor();                String val2=t.getValor();            if(sc.getValor().equals(">")){                ImprimeValor(val1+"mayor"+val2);            }            if(sc.getValor().equals("<")){                ImprimeValor(val1+"menor"+val2);            }            if(sc.getValor().equals("<=")){                ImprimeValor(val1+"menor igual"+val2);             }            if(sc.getValor().equals(">=")){                ImprimeValor(val1+">="+val2);            }      :}      |TIPO_DATO:T{:RESULT=T;:}      |open_par COND:c close_par{:RESULT=c;:} ;TIPO_DATO::= id:i{:RESULT=i; :}          |numero:n{:RESULT=n;:}          |true:t {:RESULT=t;:}          |false:f{:RESULT=t;:};

Llego la hora de explicar cada parte de el archivo cup:

• En la sección action code como expliqué anteriormente se definen las funciones que que utilizaran cuando se este recorriendo la gramática, en este caso definí la función ImprimeValor que recibe de parámetro una cadena, lo único que hace es imprimir el valor de la cadena que recibe de parámetro.

• En la sección parser code se encuentran los métodos propios del parser, aqui hice un override de la función syntax_error. 

http://openfecks.wordpress.com/

Page 11: Jlex y Cup2

Tutorial Jlex Y Java Cup

Esta función nos permite ejecutar una acción cuando el parser encuentra un error sintáctico.

• En la declaración de terminales y no terminales se definen como tipo token para poder manejar usarlos de una forma mas cómoda en las acciones de la gramática.

• Ahora vamos con las acciones en la gramatica, Cup permite agregar acciones como las que podemos ver en el ejemplo anterior estas pueden ir en cualquier lugar del lado derecho de la producción en este tipo de herramientas se sugiere ponerlas al final para evitar  ambigüedades ya que Cup toma la producción como un símbolo las en la gramática y al estar en el medio pueden haber errores de reducción o de movimiento del parser. La sintaxis para para las acciones es: {: /*acciones*/ :} donde acciones puede ser cualquier sentencia de código.

• La variable RESULT es usada por CUP para devolver el valor al padre de la producción, básicamente devuelve el valor asignado  al no terminal del lado derecho. Este valor debe ser el mismo tipo de dato que el no terminal obviamente.En el ejemplo se imprimirán los operadores relacionales en forma de texto junto con los valores de cada miembro de la expresión.

Compilación y Ejecución

Es la hora del paso final compilar y ejecutar nuestro scanner y parser.

Compilacion Jlex:

Para compilar el Jlex solo basta con escribir en consola:

$ jlex <archivojlex>

Y si todo se genera bien creara nuestra salida en código Java.

Compilacion Cup:

Para compilar Cup se presentan mas opciones como especificar la clase que lleva las constantes de los simbolos, el paquete de la clase, el nombre que deseamos para la salida del parser entre otras. Para nuestro ejemplo modificaremos el nombre de la clase con los simbolos y el nombre que recibira el parser:

$ cup ­parser <nombredelaclasedelparser> ­symbols <nombreclaseconsimbolos>  <archivodenetradacup>Para ver las demas opciones:$ man cupCódigo de ejecución:

http://openfecks.wordpress.com/

Page 12: Jlex y Cup2

Tutorial Jlex Y Java Cup

Supongamos que nuestra salida de jlex tiene el nombre: scanner.java y nuestra salida de Cup Parser.java. Para poder hacer que el scanner y el parser funcionen conjuntamente las siguientes lineas de código son útiles:

En el caso que leamos un archivo de entrada:

 File file=new File("url del archivo");        try{

        FileReader fr=new FileReader(file);        scanner lex=new scanner(fr);        Parser miParser=new Parser(lex);        miParser.parse();

        }catch(Exception e){            System.out.println(e);        }    }

La clase del scanner recibe en el constructor un Stream de caracteres en este caso lo que se leyó del archivo.

 scanner lex=new scanner(fr);

Y para integrarlo al parser solo debemos enviarlo al constructor del nuevo objeto parser creado

 Parser miParser=new Parser(lex);

Para que nuestro objeto empiece a ejecutar el parser llamamos a su metodo parse();

miParser.parse();

Si nuestra entrada no es un archivo si no un texto leído de un TextArea o algo parecido podemos usar la clase StringReader de Java en lugar del FileReader.

http://openfecks.wordpress.com/