YACC (Yet Another Compiler Compiler) LALR(1) Parser Generator 1 INDICE 1. Introducción 2. Especificaciones 3. Ambigüedad y conflictos 4. Tratamiento de errores 5. Uso con el LEX 6. Algunos consejos prácticos 2 1. Introducción E s p e c ific a c io n e s YA C C -d y.ta b .h -v y.ta b . c y.o u tp u t y.ta b .h : c o n tie n e la c o d ific a c ió n d e lo s s ím b o lo s te rm in a le s d e la g ra m á tic a y.ta b .h : c o n tie n e la ru tin a y y p a rs e () p a ra e l a n á lis is sin tá c tic o y.o u tp u t: c o n tie n e e l a u tó m a ta d e re c o n o c im ie n to s in tá c tic o 3 2. Especificaciones Declaraciones (opcional) %% Reglas de la gramática %% Funciones C (opcional) 4 Declaraciones %{ Declaraciones C %} %token token_1 … token_N %left operadores %start S (No es necesario, ya que por defecto se toma el símbolo no-terminal de la primera regla) 5 Reglas N Æ α1 | α2 | ε MÆβ N: | α1 | α2 ; / la opción ε debe ser la primera / / finalizador de reglas / M: β ; 6 Código en las reglas NÆABC MÆXY N: A B C { Código C } ; N: X { Código C } Y { Código C } ; 7 3. Ambigüedad y conflictos Una gramática ambigua genera conflictos. EJEMPLO EXPR : EXPR ‘+’ EXPR | EXPR ‘*’ EXPR | id Se generan conflictos por la indefinición de PRECEDENCIA y ASOCIATIVIDAD de los operadores 8 Conflictos por Precedencia y Asociatividad Precedencia ( (id + id) * id ) id + id * id ( id + (id * id) ) Asociatividad ( (id + id) + id ) id + id + id ( id + (id + id) ) 9 Solución de los conflictos (por defecto) Por defecto, YACC soluciona los conflictos de la siguiente forma: shift/reduce Æ reduce/reduce Æ shift escoge la primera regla declarada En cualquier caso, YACC siempre informa de los conflictos solucionados de esta forma. 10 Ejemplo (I) STMT : if ‘(‘ EXPR ‘)’ then STMT | if ‘(‘ EXPR ‘)’ then STMT else STMT | id ‘=‘ EXPR if (a+b) then if (x+y) then a=b else b=c 1. if (a+b) then [ if (x+y) then a=b ] else b=c 2. if (a+b) then [ if (x+y) then a=b else b=c ] Conflicto SHIFT/REDUCE ante el símbolo: else 11 Ejemplo (II) Por defecto, YACC resuelve el conflicto a favor del SHIFT, y nos informa de la existencia del conflicto. ¿ Es correcta esta solución por defecto ? SI, ya que la ambigüedad ha sido resuelta tal como indican los lenguajes de programación, es decir, el ELSE se asocia al IF más interno. Sin embargo, no siempre son correctas las soluciones por defecto. 12 Soluciones por defecto no satisfactorias EXPR : EXPR ‘+’ EXPR | EXPR ‘*’ EXPR | id a+b+c a*b+c Æ Æ (a + (b + c) ) (a * (b + c) ) Podemos eliminar la solución por defecto en los conflictos mediante la especificación de la precedencia y asociatividad de los operadores 13 Especificando Precedencia y Asociatividad Operadores binarios: precedencia y asociatividad Operadores unarios: solamente la precedencia En la sección de declaraciones del fichero de especificaciones podríamos indicar: %left ‘+’ ‘-’ %left ‘*’ ‘/’ %nonassoc ‘-’ (tienen igual precedencia entre ellos) (mayor precedencia que los anteriores) (mayor precedencia que los anteriores) Problema: el ‘-’ unario y el ‘-’ binario utilizan el mismo símbolo. La solución pasa por utilizar un símbolo auxiliar. 14 Ejemplo (I) %token id %left ‘+’ ‘-’ %left ‘*’ ‘/’ %nonassoc UMINUS %% EXPR : EXPR ‘+’ EXPR | EXPR ‘-’ EXPR | EXPR ‘*’ EXPR | EXPR ‘/’ EXPR | ‘-’ EXPR | id -a*b Æ Símbolo auxiliar -( a * b) incorrecto 15 Ejemplo (II) Cada regla de la gramática tiene la precedencia y asociatividad del último token de la regla, aunque se puede modificar utilizando %prec. Por tanto, el problema del ‘-’ unario puede quedar resuelto de la siguiente forma: %token id %left ‘+’ ‘-’ %left ‘*’ ‘/’ %nonassoc UMINUS %% EXPR : EXPR ‘+’ EXPR | EXPR ‘-’ EXPR | EXPR ‘*’ EXPR | EXPR ‘/’ EXPR | ‘-’ EXPR %prec UMINUS | id %% Símbolo auxiliar 16 Resumen de resolución de conflictos en YACC 1 Si no especificamos la precedencia y/o asociatividad de un operador, YACC utiliza la solución por defecto. shift/reduce Æ reduce/reduce Æ 2 shift la primera regla declarada Si especificamos la precedencia y/o asociatividad de todos los operadores, YACC utiliza esa información para solucionar los conflictos. Veamos un ejemplo en el que se mezclan ambas situaciones 17 Resolución de conflictos: Ejemplo genérico (I) %left ‘+’ %left ‘*’ %nonassoc UMINUS %% EXPR : EXPR ‘+’ EXPR | EXPR ‘-’ EXPR | EXPR ‘*’ EXPR | EXPR ‘/’ EXPR | ‘-’ EXPR %prec UMINUS | id %% 18 Resolución de conflictos: Ejemplo genérico (II) Expresiones Interpretación -a*b+c -a-b+c -a*b/c -a-b/c Æ Æ Æ Æ [ (- a) * b ] + c - [ a - (b + c) ] (- a) * (b / c) - [ a - (b / c) ] a-b+c+d a-b*c/d a-b/c+d a-b/c/d Æ Æ Æ Æ a - [ (b + c) +d ] a - [ b * (c / d) ] a - [ b / (c + d) ] a - [ b / (c / d) ] 19 4. TRATAMIENTO DE ERRORES Cuando el analizador sintáctico encuentra un error, simplemente hace la siguiente llamada: yyerror (“syntax error”); La rutina yyerror() de la librería es la siguiente: yyerror (s) char * s; { fprintf(stderr, “%s\n”, s); } 20 Modificando yyerror() yyerror (s) char * s; { fprintf(stderr, “%s\n”, s); fprintf(stderr, “Numero de linea %d\n”, n_linea); fprintf(stderr, “Simbolo terminal %s\n”, yytext); } n_linea: variable que controla el número actual de línea. yytext: cadena que almacena el último token reconocido. 21 5. Uso con el LEX %token cte %left ‘+’ ‘-’ %left ‘*’ ‘/’ %nonassoc UMINUS %% EXPR : EXPR ‘+’ EXPR | EXPR ‘-’ EXPR | EXPR ‘*’ EXPR | EXPR ‘/’ EXPR | ‘-’ EXPR %prec UMINUS | ‘(‘ EXPR ‘)’ | cte %% main() { yyparse(); } 22 Especificación LEX %{ #include “y.tab.h” /* Fichero de codificación de tokens */ %} %% [ \t\n] ; [0-9]+ { return (cte); } . { return (yytext[0]); } %% ----------------------------------------------------------------------------------------------Fichero y.tab.h #define cte 257 23 Comandos para generar el parser $ yacc -d -v file.y se generan: y.tab.h , y.tab.c , y.output $ lex file.l se genera: lex.yy.c $ cc y.tab.c lex.yy.c -ly -ll -o parser $ parser < fichero_fuente 24 6. Algunos consejos prácticos 1. Utilizar reglas recursivas por la izquierda siempre que sea posible (favorece el reconocimiento ascendente, ya que los árboles sintácticos crecen hacia la izquierda) 2. Cuidado con los tokens que son palabras claves en el lenguaje C. Véase lo que sucede en la página siguiente: 25 Problema con las palabras claves en C LEX #include y.tab.h Æ DESASTRE “if” “else” { return (if); } { return (else); } YACC STMT : if ‘(‘ EXPR ‘)’ STMT else STMT Fichero y.tab.h #define if #define else 257 258 26 Solución: utilizar otros nombres lógicos LEX #include y.tab.h “if” “else” { return (if_t); } { return (else_t); } YACC STMT : if_t ‘(‘ EXPR ‘)’ STMT else_t STMT Fichero y.tab.h #define if_t #define else_t 257 258 27