sábado, 26 de marzo de 2011

Ejemplo JLex y Cup

Siguiendo con nuestro pequeño ejemplo de un compilador, ahora agregaremos una gramática para realizar el análisis sintáctico de nuestro ejemplo de la calculadora.  Recordemos que este ejemplo esta realizado en Windows y ya tienes que tener las herramientas instaladas, si no las tienes, lee antes: Instalar JLex y Cup en W7 y Uso de JLex sin usar Cup.
Modificaremos nuestro  archivo lexico.jlex ya que  ahora lo integraremos con cup, el nuevo archivo nos quedaría de la siguiente forma:

import java_cup.runtime.Symbol;
%%  
%eofval{
  { System.exit(0); }
%eofval}
%cup
%line
%char
%state COMENTARIO
NUMBER = [1-9][0-9]*
%%
/*
* Uno de los cambios más importantes es  que ahora usamos return, que esto lo que hace es
* retornar y enviar el token a nuestro analizador léxico.  En este caso retornaremos un
* no terminal por medio de la función Symbol
*/

/* En la siguiente línea, además de pasar el no terminal NUMERO a cup, pasamos un valor
   entero, que es el valor numérico de nuestra entrada  */
<YYINITIAL> {NUMBER} { return new Symbol(sym.NUMERO, new Integer(yytext())); }
<YYINITIAL> "+" { return new Symbol(sym.MAS); }
<YYINITIAL> "-" { return new Symbol(sym.MENOS); }
<YYINITIAL> "*" { return new Symbol(sym.POR); }
<YYINITIAL> "/" { return new Symbol(sym.DIV); }
<YYINITIAL> "(" { return new Symbol(sym.PARI); }
<YYINITIAL> ")" { return new Symbol(sym.PARD); }
<YYINITIAL> ";" { return new Symbol(sym.FIN); }
<YYINITIAL> " " {}
<YYINITIAL>  [\t\r\n\f] {}
<YYINITIAL> "//" {yybegin(COMENTARIO);}
<COMENTARIO> [^\n] {}
<COMENTARIO> [\n] {yybegin(YYINITIAL); yychar=0;}
<COMENTARIO> .|\n {if(!(yytext().equals("\n")))
                                                                              System.out.println("error lexico en "  + yyline + "," + yychar + " No se reconoce " + yytext());
                                                               yychar=0; }

Luego de modificar nuestro archivo léxico.jlex, definimos la gramática que utilizaremos en para el análisis sintáctico.  La gramática es la siguiente

E ->        E + T
                | T
T ->        T - F
                | F
F ->        F * G
                | G
G ->        G / H
                | H
H ->        (E)
                | n

La gramática anterior nos produce los terminales y no terminales para aceptar expresiones que contengan operaciones aritméticas (+,-,* y /).   Ahora definimos nuestro archivo sintactico.cup, el cual contendrá lo siguiente:

/* Importamos las clases necesarias del paquete cup.jar */
import java_cup.runtime.*;
/**
* Aquí ponemos el código que se usará para comenzar a parsear la entrada.
*/
parser code {:
public static void main(String args[]) throws Exception {
// La clase Yylex es creada por el analizador léxico

new parser(new Yylex(System.in)).parse();
}
:}
/* Aquí especificamos los terminales del lenguaje. */
terminal MAS, MENOS, POR, DIV, PARI, PARD, FIN;
/**
* Este terminal tiene un valor entero. Recuerda que le dábamos el valor
* en el código del analizador léxico, al darle como parámetro un valor
* entero al objeto Symbol.
*/
terminal Integer NUMERO;
/* Lista de no terminales. */
non terminal expr_list, expr_part;
/**
* Aquí están los no terminales con valor entero, que son con los que
* podemos hacer cálculos, y podemos escribirlos de la forma expr_e:l
* (por ejemplo, no se podría hacer expr_list:l, ya que a ese no
* terminal no le damos valor.
*/
non terminal Integer expr_e;
non terminal Integer expr_t;
non terminal Integer expr_f;
non terminal Integer expr_g;
non terminal Integer expr_h;
/* Aquí especificamos la precedencia de los operadores. */
precedence left MAS;
precedence left MENOS;
precedence left POR;
precedence left DIV;
/**
* Ahora comenzamos con las reglas de producción.
*/
/**
* Estas dos reglas son nuevas. Nos sirven para encadenar varias
* expresiones separadas por un ';'
*/
expr_list ::= expr_list expr_part | expr_part
                    ;
/*
*Al  hacer "expr_e:e", podemos usar esta "e" para realizar cualquier operacion
* ya que cup guarda el valor que trae expr_e en la variable e, en este caso un numero.
*/
expr_part ::= expr_e:e {: System.out.println("= "+e); :} FIN
                    ;
/*
* Como nos damos cuenta en las siguientes lineas, podemos realizar las operaciones
* con los valores obtenidos en la gramatica, con las variables l y r y estas las
* retorna, guardando en el no terminal expr_e
*/
expr_e ::= expr_e:l MAS expr_t:r {: RESULT=new Integer(l.intValue() + r.intValue()); :}
                  | expr_t:e {: RESULT=e; :}
                  ;
expr_t ::= expr_t:l MENOS expr_f:r {: RESULT=new Integer(l.intValue() - r.intValue()); :}
                  | expr_f:e {: RESULT=e; :}
                  ;
expr_f ::= expr_f:l POR expr_g:r {: RESULT=new Integer(l.intValue() * r.intValue()); :}
                  | expr_g:e {: RESULT=e; :}
                  ;
                  expr_g ::= expr_g:l DIV expr_h:r {: RESULT=new Integer(l.intValue() / r.intValue()); :}
                  | expr_h:e {: RESULT=e; :}
;
expr_h ::= PARI expr_e:e PARD {: RESULT=e; :}
                  | NUMERO:n {: RESULT=n; :}
                  ;

Ya definido nuestros archivos léxico.jlex y sintáctico.cup, compilamos con los siguientes comandos::

·        Compilamos  el lexico.jlex
java JLex.Main lexico.jlex

·         Renombramos el archive generado, lexico.jlex.java ya Yylex.java

·         Compilamos el sintáctico.cup
java java_cup.Main sintactico.cup

·         Compilamos los arhivos generados, parser.java, sym.java y Yylex.java con el siguiente comando.
·         ]javac -d . parser.java sym.java Yylex.java


Listo, para ejecutar, necesitamos un archivo por ejemplo entrada.txt con algunas operaciones aritméticas, por ejemplo

1 + 2 * 3/2;
1+3*(4-2)/2;
3+4;

Ejecutamos con el comando:

java parser 0<entrada.txt

Listo, espero les sirva este pequeño ejemplo, pueden descargar el ejemplo completo en Ejemplo JLex y Cup


5 comentarios:

  1. Gracias, creo que gracias a ti, pasare mi materia de programación de sistemas. Muchas gracias. Muy buen post

    ResponderEliminar
  2. hola solo con una pregunta para poder hacer q el programa con lex y cuo ya compilado en java muestre por ejemplo int cont=0;

    while(cont<10){

    x[cont]=cont*cont;

    cont=cont+1;
    y lo pase a 3 direcciones así
    L1: if(cont<10) goto L2;

    goto L3;

    L2:

    t1=cont*cont;

    x[cont]=t1;

    t2=cont+1;

    cont=t2;

    goto L1;

    L3;
    como podría hacerlo

    ResponderEliminar
  3. Quiero usar CUP, pero no me gusta -ni un poquito- Java. Y quiero usar CUP para hacer un lenguaje lo más sencillo posible, o sea, lo más alejado de Java que pueda. Conoces algún otro analizador sintáctico? Si no, no me quedará más remedio que -como primer aproximación- usar flex para traducir mi lenguaje a lenguaje C... y compilarlo con gcc...

    ResponderEliminar
    Respuestas
    1. Puedes utilizar Bison (en C), Irony(en .NET) o Gold Parser (para varios lenguajes)

      Eliminar
  4. Te agradecería lo que puedas aportarme. Saludos!

    ResponderEliminar