La siguiente receta explica con un ejemplo como construir un analizador léxico con JFlex y un analizador sintáctico y semántico con CUP (obviamente para Java). Además, muestra el uso de producciones de error.
Requisitos
jflex - analizador léxico para Java
cup - analizador sintáctico/semántico LALR para Java
Construcción
El ejemplo consiste en un conversor de unidades métricas. Admitiría textos de entradas del siguiente tipo:
Entrada.txt
Km 1 mm;
dm 3 dm;
Hm 393 m;
Dm 54 cm, mm 3429 dm;
Km 23 Dm;
dm 322 cm, Hm 0923 mm;
y devuelve en consola:
1 Km son 1000000 mm;
3 dm son 3 dm;
393 Hm son 39300 m;
...
Un archivo flex tiene la siguiente estructura (cada sección se separa mediante %%) :
Código de usuario
Opciones y declaraciones
Reglas léxicas
Conversor.lex
//* ------------Sección codigo-usuario -------- */
import java_cup.runtime.*;
%%
/*- Sección de opciones y declaraciones -*/
/*-OPCIONES --*/
/* Nombre de la clase generada para el analizador lexico */
%class conversor
/* Indicar funcionamiento autonomo*/
%standalone
%8bit
/* Acceso a la columna y fila actual de analisis CUP */
%line
%column
/* Habilitar la compatibilidad con el interfaz CUP para el generador sintactico*/
%cup
/*-- DECLARACIONES --*/
%{/*Crear un nuevo objeto java_cup.runtime.Symbol con información sobre el token actual sin valor*/
private Symbol symbol(int type){
return new Symbol(type,yyline,yycolumn);
}
/* Crear un nuevo objeto java_cup.runtime.Symbol con información sobre el token actual con valor*/
private Symbol symbol(int type,Object value){
return new Symbol(type,yyline,yycolumn,value);
}
%}
/*-- MACRO DECLARACIONES --*/
LineTerminator = \r|\n|\r\n
WhiteSpace = {LineTerminator} | [ \t\f]
//finConversion = [,;]
num_int = [0-9]+
%%
/*-------- Sección de reglas lexicas ------ */
<YYINITIAL> {
//{finConversion}{ return symbol.FCONVERSION}
"," { return symbol(sym.COMA);}
";" { return symbol(sym.PUNTOCOMA);}
"mm" { return symbol(sym.MM); }
"cm" { return symbol(sym.CM); }
"dm" { return symbol(sym.DCM); }
"m" { return symbol(sym.M); }
"Dm" { return symbol(sym.DCAM); }
"Hm" { return symbol(sym.HM); }
"Km" { return symbol(sym.KM); }
{num_int} { return symbol(sym.NUMBER, new Integer(yytext())); }
{WhiteSpace} { }
. {System.out.println("token ilegal <" + yytext()+ ">  linea: " + (yyline+1) + " columna: " + (yycolumn+1));}
}
En cuanto a un fichero CUP se pueden diferenciar cinco partes:
Especificaciones de “package” e “imports”.
Componentes de código de usuario.
Lista de símbolos de la gramática(terminalesno terminales).
Declaraciones de precedencia.
Especificación de la gramática.
Conversor.cup
/*Sección de declaraciones package e imports*/
import java_cup.runtime.*;
/* Sección componentes de código de usuario */
parser code {:
public void report_error(String message, Object info) {
StringBuffer m = new StringBuffer("Error");
System.out.println("Mensaje: "+message);
System.out.println("info: "+info.toString());
if(info instanceof java_cup.runtime.Symbol) {
java_cup.runtime.Symbol s=((java_cup.runtime.Symbol)info);
/* Comprueba si el numero de línea es mayor o igual que cero */
if(s.left >= 0) {
m.append(" en linea "+(s.left+1));
/*Comprueba si el numero de columna es mayoro igual que cero */
if (s.right >= 0)
m.append(", y columna "+(s.right+1));
}
}
m.append(" : "+message);
System.err.println(m);
}
public void report_fatal_error(String message, Object info) {
report_error(message, info);
System.exit(1);
}
public void conversion(String a, String b, int n){
int c=0,d=0;
System.out.print(n+" ");
if (a=="Km"){ c=6; System.out.print("Km"); }
else if (a=="Hm"){ c=5; System.out.print("Hm"); }
else if (a=="Dm"){ c=4; System.out.print("Dm"); }
else if (a=="m"){ c=3; System.out.print("m"); }
else if (a=="dm"){ c=2; System.out.print("dm"); }
else if (a=="cm"){ c=1; System.out.print("cm"); }
else if (a=="mm"){ c=0; System.out.print("mm"); }
if (b=="Km") d=6;
else if (b=="Hm") d=5;
else if (b=="Dm") d=4;
else if (b=="m") d=3;
else if (b=="dm") d=2;
else if (b=="cm") d=1;
else if (b=="mm") d=0;
System.out.print(" son "+n*Math.pow(10,c-d)+" ");
}
:};
/* Declaración de la lista de símbolos de la gramática */
/* Produciones de flujo normal */
terminal COMA, PUNTOCOMA, MM, DCM, CM, M, DCAM, HM, KM;
terminal Integer NUMBER;
non terminal Object programa, linea, expresiones, medida;
/* Declaración de la gramática */
programa ::= programa linea | linea;
linea ::= error NUMBER medida PUNTOCOMA {: parser.report_error("revise la sintaxis",null); :} |
medida:m1 NUMBER:n medida:m2 PUNTOCOMA {: parser.conversion(m1.toString(),m2.toString(),n.intValue());
System.out.println(m2); :} |
expresiones medida:m1 NUMBER:n medida:m2 PUNTOCOMA
{: parser.conversion(m1.toString(),m2.toString(),n.intValue());
System.out.println(m2); :} ;
expresiones ::= expresiones medida:m1 NUMBER:n medida:m2 COMA
{: parser.conversion(m1.toString(),m2.toString(),n.intValue());
System.out.println(m2); :} |
medida:m1 NUMBER:n medida:m2 COMA {: parser.conversion(m1.toString(),m2.toString(),n.intValue());
System.out.println(m2); :} |
error NUMBER medida COMA {: parser.report_error("revise las medidas en mayusculas",null); :};
medida ::= KM {: RESULT="Km"; :} | HM {: RESULT="Hm"; :} | DCAM {: RESULT="Dm"; :} |
M {: RESULT="m"; :} | DCM {: RESULT="dm"; :} |
CM {: RESULT="cm"; :} | MM {: RESULT="mm"; :};
Necesitas un fichero que cree un objeto parser y comience el análisis.
Main.java
import java_cup.runtime.Symbol ;
import java.io.* ;
class Main {
static boolean do_debug_parse = false ;
static public void main ( String [] args ) throws java . io . IOException {
parser parser_obj = new parser ( new conversor ( new FileReader ( args [ 0 ])));
Symbol parse_tree = null ;
try {
if ( do_debug_parse )
parse_tree = parser_obj . debug_parse ();
else parse_tree = parser_obj . parse ();
System . out . println ( "Entrada correcta" );
} catch ( Exception e ) {
System . out . println ( "Horror" );
}
}
}
Makefile
all: java
javac *.java
java:
jflex *.lex
cup *.cup
clean:
$(RM) *.class
$(RM) parser.java sym.java
$(RM) *~
Referencias
JFlex
CUP