Inicio con las palabras de mi antigua auxiliar de compi1 "Aura" la cual al consultarle en clase nos dijo "Yo no voy a decirles como recuperarse de errores en CUP seria hacerles el proyecto".
Bueno como primer punto su rol de auxiliar era al menos "dar una luz" con respecto a dicho tema. pero al igual que muchos en nuestra facultad les gusta hacer que otros paguen lo que ellos sufrieron.
En fin si la dichosa recuperacion de errores fuese algo largo y tedioso podria tener razon en no pasar el chivo.
asi que para no hacerla mas larga a continuacion les muestro 3 maneras de recuperar erroes e informar de los mismo en CUP de las cuales solo la primera es la correcta para respetar nuestra Gramatica.
Metodo #1 (el ideal, el que cup recomienda)
basta con reescribir un dos metodos de cup (agregar lo siguiente en la seccion parser code del archivo .cup)
A)
public void syntax_error(Symbol s){
System.out.println("La Cadena:" + s.value+" en la Linea:"(s.right+1) +" ,Columna: "+s.left+ "esta fuera de contexto." );}
B)
public void unrecovered_syntax_error(Symbol s) throws java.lang.Exception{System.out.println("La Cadena:" + s.value+" en la Linea:"(s.right+1) +" ,Columna: "+s.left+ "esta fuera de contexto." );
}
public void syntax_error(Symbol s){
System.out.println("La Cadena:" + s.value+" en la Linea:"(s.right+1) +" ,Columna: "+s.left+ "esta fuera de contexto." );}
B)
public void unrecovered_syntax_error(Symbol s) throws java.lang.Exception{System.out.println("La Cadena:" + s.value+" en la Linea:"(s.right+1) +" ,Columna: "+s.left+ "esta fuera de contexto." );
}
y ya tendremos el error con su valor, linea y columna exacta.
ahora como llamamos al metodo? R// agregando un "|error" en nuestras producciones pues el metodo anteriormente descrito es llamado automaticamente cuando se encuentra un error. asi que si necesitamos por ejemplo satisfacer una gramatica que reconosca varios grupos de numeros separados por comas y que al terminar la cadena venga un punto y coma(1,2,4,87,4,3;) nos quedaria:
terminal numero, coma, p_coma;
non terminal NUMERO, MAS_NUMERO, MAS_LINEA;
start with NUMERO;
NUMERO::=numero MAS_NUMERO
|error;
MAS_NUMERO::=coma numero MAS_NUMERO
|p_coma MAS_LINEA
|error;
MAS_LINEA::=|numero MAS_NUMERO
|error;
non terminal NUMERO, MAS_NUMERO, MAS_LINEA;
start with NUMERO;
NUMERO::=numero MAS_NUMERO
|error;
MAS_NUMERO::=coma numero MAS_NUMERO
|p_coma MAS_LINEA
|error;
MAS_LINEA::=|numero MAS_NUMERO
|error;
y asi ya tenemos el informe de errores. pero el problema que tendran es que al utilizar solo la opcion "|error" la gramatica en cuanto encuentre un error dara por terminada la entrada y todo lo que venga por delante no sabra que es y por consecuencia llevara al metodo B(unrecovered_sintax_error) y aunque luego vengan mas numeros separados correctamente. nunca podra saberse. y menos aun si vienen mas grupos de numeros de manera correcta.
Asi que es IMPORTANTE definir luego de un error una valvula de escape que permita seguir analizando. en el ambito de las presentaciones de compi1 a esto le llaman "Modo Panico". para nuestro ejemplo la valvula de escape es el punto y coma (). pero en el caso que no tuviesemos el amado punto y coma (por algo java lo utiliza). podemos reconocer el salto de linea desde JFlex(o Jlex) y enviarlo como un token de escape. no es muy optimo pues tendriamos que validar la llegada de 2 o mas saltos de linea como uno solo (coincidencia que VB.net use el salto de linea?)..........Regresando al tema nuestro ejemplo quedaria asi
start with NUMERO;
NUMERO::=numero MAS_NUMERO
|error p_coma;
MAS_NUMERO::=coma numero MAS_NUMERO
|p_coma MAS_LINEA
|error p_coma;
MAS_LINEA::=|numero MAS_NUMERO
|error p_coma;
NUMERO::=numero MAS_NUMERO
|error p_coma;
MAS_NUMERO::=coma numero MAS_NUMERO
|p_coma MAS_LINEA
|error p_coma;
MAS_LINEA::=|numero MAS_NUMERO
|error p_coma;
con esto recuperamos los errores entre lineas. pero en el caso que venga un error. la gramatica buscara hasta encontrar un punto y coma. y no revisara lo demas. Asi que aun no se tienen los puntos de la recuperacion de errores completa. para solucionar esto lo que se debe de hacer es agregarle luego de la valvula de escape un NO-Terminal al cual irse luego de recuperarse de un error . quedando asi
start with NUMERO;
NUMERO::=numero MAS_NUMERO
|error p_coma MAS_LINEA;
MAS_NUMERO::=coma numero MAS_NUMERO
|p_coma MAS_LINEA
|error p_coma MAS_LINEA;
MAS_LINEA::=|numero MAS_NUMERO
|error p_coma MAS_LINEA;
NUMERO::=numero MAS_NUMERO
|error p_coma MAS_LINEA;
MAS_NUMERO::=coma numero MAS_NUMERO
|p_coma MAS_LINEA
|error p_coma MAS_LINEA;
MAS_LINEA::=|numero MAS_NUMERO
|error p_coma MAS_LINEA;
y con esto ya tendremos recuperacion de errores total. Pero si somos observadores nos daremos cuenta que si viene un error la gramatica automaticamente busca un punto y coma. y por consecuencia se salta todos los numeros y comas que esten entre el error y el punto y coma. Y aunque esa es la idea se puede mejorar para que reconosca errores pero no se salte tanto. como? pues podemos utilizar los terminales que se producen como otra valvula de escape. Entonces nos quedaran 2 niveles de escape. 1-los terminales de la producción y 2-el terminal de fin de linea
La gramatica final quedaria asi:
start with NUMERO;
NUMERO::=numero MAS_NUMERO
|error NUMERO
|error p_coma MAS_LINEA;
MAS_NUMERO::=coma NUMERO
| p_coma MAS_LINEA
|error MAS_NUMERO;
MAS_LINEA::=|numero MAS_NUMERO
|error MAS_LINEA;
NUMERO::=numero MAS_NUMERO
|error NUMERO
|error p_coma MAS_LINEA;
MAS_NUMERO::=coma NUMERO
| p_coma MAS_LINEA
|error MAS_NUMERO;
MAS_LINEA::=|numero MAS_NUMERO
|error MAS_LINEA;
Aunque esta ultima parte es por mero amor al arte. se demuestran las capacidades de cup si respetamos su forma de trabajar. vease que en la produccion numero tenemos como principal v. de escape a un numero y como secundaria al punto y coma. Mientras que en la produccion de MAS_NUMERO tenemos como primeras valvulas de escape a coma y p_coma.
en todos los casos cup se recuperara de la mayor cantidad de errores posibles y sin tantas cosas que ponerle.
MÉTODO #2 (modo pánico y mal hecho)
es simplemente lo explicado en el metodo #1 pero sin la re-escritura de metodos. esta sea la mayor explicacion que conseguiremos de un auxiliar en la facultad. la única ventaja que tiene es que podemos describir exactamente que se esperaba que viniese pero por mas que le demos String.valueOf(a) nunca sabremos cual es la cadena que genero el error (siempre pondrá null)
ej:
NUMERO::=numero MAS_NUMERO
|error:a{:System.out.println(a.right,aleft,String.valueOf(a),"se esperaba un numero");:};
MAS_NUMERO::=coma NUMERO
|p_coma MAS_LINEA
|error:a{:System.out.println(a.right,aleft,String.valueOf(a),"se esperaba una coma");:};
MAS_LINEA::=|numero MAS_NUMERO
|error:a{:System.out.println(a.right,aleft,String.valueOf(a),"se esperaba numero o fin de archivo");:};
este metodo puede ser manipulado para que se recupere de errres como el caso #1 pero tendran un archivo lleno de caracteres que les confundira todo
METODO #3 (mazoquismo merecido)
y para los que les gusta hacer todo por el camino del sufrimiento tenemos la alternativa mas dificil. pero que nos dara la mayor descripcion de errores.
El metodo consiste en generar todas las producciones posibles (hacer que la gramatica acepte todo) pero reportar cuando acepta algo que no quisieramos.
ej:
NUMERO::=numero MAS_NUMERO
|coma:a{:System.out.println(a.right,aleft,String.valueOf(a),"se esperaba un numero y vino una coma");:}
|p_coma:a{:System.out.println(a.right,aleft,String.valueOf(a),"se esperaba un numero pero vino una punto y coma");:}
|{:System.out.println(a.right,aleft,String.valueOf(""),"se esperaba un numero y vino fin de archivo");:};
MAS_NUMERO::=coma NUMERO
|p_coma MAS_LINEA
|numero:a{:System.out.println(a.right,aleft,String.valueOf(a),"se esperaba coma y vino numero");:}
|{:System.out.println(a.right,aleft,String.valueOf(""),"se esperaba coma y vino fin de archivo");:};
MAS_LINEA::=|numero MAS_NUMERO
|coma:a{:System.out.println(a.right,aleft,String.valueOf(a),"se esperaba un numero y vino una coma");:}
|p_coma:a{:System.out.println(a.right,aleft,String.valueOf(a),"se esperaba un numero pero vino una punto y coma");:};
|error:a{:System.out.println(a.right,aleft,String.valueOf(a),"se esperaba un numero");:};
MAS_NUMERO::=coma NUMERO
|p_coma MAS_LINEA
|error:a{:System.out.println(a.right,aleft,String.valueOf(a),"se esperaba una coma");:};
MAS_LINEA::=|numero MAS_NUMERO
|error:a{:System.out.println(a.right,aleft,String.valueOf(a),"se esperaba numero o fin de archivo");:};
este metodo puede ser manipulado para que se recupere de errres como el caso #1 pero tendran un archivo lleno de caracteres que les confundira todo
METODO #3 (mazoquismo merecido)
y para los que les gusta hacer todo por el camino del sufrimiento tenemos la alternativa mas dificil. pero que nos dara la mayor descripcion de errores.
El metodo consiste en generar todas las producciones posibles (hacer que la gramatica acepte todo) pero reportar cuando acepta algo que no quisieramos.
ej:
NUMERO::=numero MAS_NUMERO
|coma:a{:System.out.println(a.right,aleft,String.valueOf(a),"se esperaba un numero y vino una coma");:}
|p_coma:a{:System.out.println(a.right,aleft,String.valueOf(a),"se esperaba un numero pero vino una punto y coma");:}
|{:System.out.println(a.right,aleft,String.valueOf(""),"se esperaba un numero y vino fin de archivo");:};
MAS_NUMERO::=coma NUMERO
|p_coma MAS_LINEA
|numero:a{:System.out.println(a.right,aleft,String.valueOf(a),"se esperaba coma y vino numero");:}
|{:System.out.println(a.right,aleft,String.valueOf(""),"se esperaba coma y vino fin de archivo");:};
MAS_LINEA::=|numero MAS_NUMERO
|coma:a{:System.out.println(a.right,aleft,String.valueOf(a),"se esperaba un numero y vino una coma");:}
|p_coma:a{:System.out.println(a.right,aleft,String.valueOf(a),"se esperaba un numero pero vino una punto y coma");:};
Como ven en este se sabe que vino (se asume como error aunque lo acepte) y que se deseaba que viniese. El problema con este en especial es que forzamos la gramatica a que acepte todo. y esto seria un error de concepto
Como Notas Finales cabe recordar que a.right y s.rigth dan la linea de un simbolo asi como a.left y s.left dan la columna del mismo.
tambien que no es error ni chapus mio el poner sentencias como "|numero"
ya que esto para cup significa (épsilon)|numero. y con épsilon se refiere a "nada"
no se si existen mas maneras de recuperar e informar errores estas son las que yo inferi/aprendi gracias a mi bella auxiliar y su negligencia y rencor.
espero ayude a alguien o al menos me recuerde que hacer para cuando lleve proyectos de compi2
Vs pero siempre se recomienda agragar un terminal en la producción de error va? es que si pongo solo:
ResponderEliminar|error
;
Da clavo con shift/reduce
es que si pones solo |error. No podras recuperarte de errores y cup no sabra a donde ir.
EliminarAsi que siempre trata de poner un terminal luego de error o si lo queres hacer recursivo pone el no terminal que produce
ej:
E::=num
|error E;
asi se quedara saltando errores hasta que encuentre un terminal "num"
Gracias vs, hehe mirá otra pregunta, intente ponerle un System.out ponele de este modo:
EliminarCONTENIDO ::= tag .... cierre_tag
| error cierre_tag {:System.out.println("Error en el tag portada"):} // para que obvie toda una etiqueta
;
Pero nunca se ejecuta el código, no sé si el código java se ejecuta ya de último cuando se verifica que no haya errores....?
ese mensaje solo se ejecutara Luego de que encuentre errores y ademas luego de que seguido de algun error venga el cierre_tag
EliminarPD. Una gramática regular dá ventaja sobre una libre de contexto al momento de indicar con certeza donde está el error?
ResponderEliminarsi vos, de hecho si mantenes tus gramaticas regulares, aunque estas queden mas largas que una libre de contexto, te sera mas facil recuperarte/identificar errores asi como poder hacer tus acciones. Yo siempre trato de usar gramticas regulares.
ResponderEliminarOk gracias me ayudó bastante el Blog
ResponderEliminarq excelente explicacion de recuperacion de errores ... :D
ResponderEliminar