Sunteți pe pagina 1din 31

151

YACC
! Yacc es un generador de analizadores sintcticos LALR. ! Yacc significa otro compilador de compiladores ms (Yet Another Compiler-Compiler). ! Construccin de un traductor usando Yacc:
traduce.y YACC y.tab.c

y.tab.c C

a.out

entrada a.out

salida

Partes de las que consta un programa fuente en Yacc: {declaraciones} %% {reglas} %% {rutinas de apoyo en C} Declaraciones 1) Declaraciones ordinarias en C, delimitadas por %{ y %}. 2) Declaraciones de los componentes lxicos de la gramtica (esto se explica ms adelante). Podra estar vaca.

152

Reglas de traduccin.- Cada una de ellas consta de una produccin de la gramtica y la accin semntica asociada. Es decir, <lado izquierdo> <alt 1> | <alt 2> | ... | <alt n> pasara a ser en Yacc: <lado izquierdo> : <alt 1> {accin semntica 1} | <alt 2> {accin semntica 2} ... | <alt n> {accin semntica n} ; ! Un carcter simple entre comillas c se considera como el smbolo terminal c. ! Las cadenas sin comillas de letras y dgitos no declaradas como componentes lxicos se consideran no terminales. ! El primer lado izquierdo se considera como smbolo inicial por defecto, o bien se declara : %start smbolo ! Una accin semntica es una secuencia de proposiciones en C, dnde $$ se refiere al valor del atributo asociado con el no terminal del lado izquierdo, mientras que $i se refiere al valor asociado con el i-simo smbolo gramatical del lado derecho. Se ejecuta siempre que se reduzca por la produccin asociada. Por defecto es {$$=$1;}.

153

Una accin semntica no tiene por qu venir al final de su regla. Yacc permite que una accin sea escrita en mitad de una regla. Esta regla devuelve un valor, accesible de forma normal por las acciones que estn a su derecha, que pueden acceder a los valores devueltos por los smbolos a su izquierda. Ejemplo: A : B {$$=1;} C {x=$2; y=$3;}

El efecto es poner x a 1 e y al valor devuelto por C. ! Las acciones que no terminan una regla son manejadas por Yacc como si fueran un nuevo nombre de no-terminal, con una nueva regla para l con el string vaco en su parte derecha, y, como accin de esa regla, ella misma. Es decir, el ejemplo anterior lo maneja como si se hubiera escrito as: $ACT : A : /* empty */ {$$=1;}; B $ACT C {x=$2; y=$3;};

NOTA: Puede haber conflictos cuando ocurre una accin interior en una regla antes de que el parser pueda estar seguro de qu regla est siendo reducida.

154

En muchas aplicaciones, la salida no viene dada directamente con las acciones; en su lugar, una estructura de datos, tal como un rbol de anlisis, se construye en memoria y se aplican transformaciones en l antes de que la salida sea generada. Los rboles de anlisis son particularmente fciles de construir, si se tienen rutinas para realizar y mantener la estructura de rbol deseada. Ejemplo.- Tenemos una funcin C que se llama nodo, de forma que nodo(L,n1,n2) crea un nodo con etiqueta L y descendientes n1 y n2, y devuelve el ndice del nuevo nodo. El rbol de anlisis podra ser realizado suministrando acciones en la especificacin como expr : expr '+' expr {$$=nodo('+',$1,$3);}

Pueden definirse tambin otras variables para ser usadas por las acciones. Tanto las declaraciones como las definiciones deben aparecer en la seccin de declaraciones, entre %{ y %}. Tienen mbito global. Deben evitarse los nombres de variables que empiecen por yy, pues los nombres de variables internas de Yacc comienzan de esta forma todos.

155

Rutinas de apoyo en C.- Se debe proporcionar un anlisis lxico, yylex(), que produzca componentes lxicos con sus valores de atributos asociados, es decir, los componentes declarados en la primera seccin de YACC, y los valores de atributos en la variable yylval. Adems, se pueden aadir otros procedimientos, como rutinas de recuperacin de errores. ! Esta seccin tambin puede estar vaca. As, la especificacin ms pequea legal de YACC es %% reglas Los blancos, tabuladores y newlines son ignorados siempre que no aparezcan entre nombres. Los comentarios pueden aparecer en cualquier lugar dnde un nombre sea legal, entre /* */, como en C. Los nombres pueden ser de longitud arbitraria y pueden estar formados por letras (distinguiendo maysculas de minsculas), puntos, signos de subrayado y dgitos. Los nombres usados en el cuerpo de una regla de gramtica pueden representar tokens o smbolos no terminales. ! Los nombres que representan tokens deben declarados. Esto se hace de la siguiente forma %token nombre1 nombre2 ser

en la seccin de declaraciones.

156

Ejemplo.- Construccin de una calculadora sencilla que lea una expresin aritmtica, la evale, y despus imprima su valor numrico. Gramtica: EE+T | T TT*F | F F(E)| digito %{ #include <ctype.h> %} %token DIGITO

%% lnea : expr '\n' {printf("%d\n");} ; expr : | ; expr '+' tmino trmino {$$=$1+$3;}

trmino : trmino '*' factor | factor ; factor | ; : '(' expr ')' DIGITO

{$$=$1*$3;}

{$$=$2;}

%% yylex() { int c; c=getchar(); if (isdigit(c)) { yylval=c-'0'; return DIGITO; } return c; }

157

LEX Y YACC ! Lex genera un programa yylex(), que es llamado por el programa principal de Yacc por defecto. ! Cada regla Lex debera terminar con return(token); devolviendo el valor apropiado de token. ! La interaccin se realiza de la siguiente forma: yacc y1.y lex l1.l cc y.tab.c lex.yy.c

-ly

-ll

! Tambin podra compilarse el fichero de salida de lex como parte del fichero de salida yacc, poniendo: #include lex.yy.c en la ltima seccin de yacc, y omitiendo en el comando de compilacin cc el fichero lex.yy.c. ! Las libreras yacc (-ly) deben ser cargadas antes que las de lex, para que el programa principal invoque al parser. ! No importa el orden de generacin de los programas lex y yacc. ! Usando la opcin d de yacc se genera y.tab.h, de definicin de tokens, que despus puede ser incluido en el programa lex, poniendo %{ #include y.tab.h %} en la seccin de definiciones del fichero de entrada lex.

158

CMO TRABAJA EL PARSER


El analizador sintctico producido por YACC es una mquina de estados finitos con una pila. Adems, el analizador lxico es capaz de leer y recordar el siguiente token de entrada (token de lookahead). El estado actual es siempre el del tope de la pila. Los estados vienen etiquetados con enteros pequeos; inicialmente, la mquina est en el estado 0, la pila slo contiene ese estado y an no se ha ledo ningn token de lookahead. La mquina puede realizar cuatro acciones: desplaza (shift), reduce (reduce), acepta (accept) y error (error). Un movimiento es realizado como sigue: 1. Segn el estado actual, el parser decide si necesita un token lookahead para decidir qu accin debera ser hecha; si necesita uno y no lo tiene, llama a yylex para obtener el siguiente token. 2. Usando el estado actual y el token looahead si se necesita, el parser decide su siguiente accin, y la lleva a cabo. Con esto puede meter estados en la pila, sacar estados de la pila, y procesar o no el token lookahead.

159

La accin desplaza (shift) es la ms comn que realiza el parser. Para ella se necesita un token de anticipacin. Si estamos en el estado i, y la accin en l es TOK shift j significa que, en el estado i, con el token de anticipacin TOK, el actual estado es introducido en la pila, el estado j se convierte en el estado actual (tope de la pila) y el token TOK es borrado. Con la accin reduce (reduce) puede ser necesario consultar el smbolo de anticipacin para decidir si reducir, pero usualmente no. Las acciones reduce estn asociadas con reglas de la gramtica, representadas tambin por medio de enteros pequeos. Si en el estado en el que estamos pone reduce k, y la regla n k es, por ejemplo A : x y z; se sacan de la pila tantos estados como smbolos tenga la parte derecha de la regla (tres en este caso). Despus, usando el estado que ha quedado en el tope y el smbolo de la parte izquierda de la regla (A en este caso), obtenemos el nuevo estado, que introducimos en la pila, despus de realizar un desplazamiento de A.

160

Este nuevo estado lo obtenemos a partir de una accin que hay en el estado que qued al descubierto: A goto l que convierte a l en el estado actual. ! La accin reduce es tambin importante en el tratamiento de acciones dadas por el usuario y valores: Cuando se reduce con una regla, el cdigo dado con la regla es ejecutado antes de que la pila sea ajustada. Adems de la pila que maneja los estados, otra pila, paralela a sta, contiene los valores devueltos por el analizador lxico y las acciones. Cuando se produce un shift yylval se copia en la pila de valores. Despus de volver del cdigo de usuario, se lleva a cabo la reduccin. Cuando se realiza un goto, yyval se copia en la pila. Las pseudovariables $1, $2, ... remiten a la pila. La accin acepta (accept) indica que la entrada ha sido leda y que es correcta. La accin error (error) indica un lugar dnde el parser no puede continuar de acuerdo con la especificacin. El parser produce un error, intenta recuperar la situacin y resumir el anlisis. ! Invocando Yacc con v, se crea el fichero y.output, que no da una descripcin del parser.

161

ejem1.y %{ %} %start e %token %% e : e MAS t | t; t : t POR f | f ; f : PARIZ e PARDE | ID ; POR MAS PARIZ PARDE ID

ejem1.l %{ #include "y.tab.h" extern int yylval; %} %% "*" "+" "(" ")" [0-9]* "\n"

return(POR); return(MAS); return(PARIZ); return(PARDE); {yylval=atoi(yytext);return(ID);}; return(0);

y.tab.h (generado por Yacc) # # # # # define define define define define POR 257 MAS 258 PARIZ 259 PARDE 260 ID 261

162

y.output (generado por Yacc)


state 0 $accept : _e $end PARIZ shift 4 ID shift 5 . error e t f goto 1 goto 2 goto 3 . significa que se realiza esa accin en todo caso El carcter _ indica lo que ha sido procesado, y lo que an falta por venir, en cada regla.

state 1 $accept : e_$end e : e_MAS t $end accept MAS shift 6 . error state 2 e : t :

t_ (2) t_POR f

POR shift 7 . reduce 2 state 3 t : .

f_

(4)

reduce 4

state 4 f :

PARIZ_e PARDE

PARIZ shift 4 ID shift 5 . error e t f goto 8 goto 2 goto 3

state 5 f : .

ID_

(6)

reduce 6

163

state 6 e :

e MAS_t

PARIZ shift 4 ID shift 5 . error t goto 9 f goto 3 state 7 t : t POR_f PARIZ shift 4 ID shift 5 . error f goto 10 state 8 e : e_MAS t f : PARIZ e_PARDE MAS shift 6 PARDE shift 11 . error state 9 e : e MAS t_ t : t_POR f POR shift 7 . reduce 1 state 10 t : t POR f_

(1)

(3)

. reduce 3 state 11 f : PARIZ e PARDE_ . reduce 5

(5)

7/127 terminals, 3/600 nonterminals 7/300 grammar rules, 12/1000 states 0 shift/reduce, 0 reduce/reduce conflicts reported 7/601 working sets used memory: states,etc. 28/2000, parser 8/4000 8/3001 distinct lookahead sets 4 extra closures 13 shift entries, 1 exceptions 6 goto entries 3 entries saved by goto default Optimizer space used: input 37/2000, output 16/4000 16 table entries, 4 zero
maximum spread: 261, maximum offset: 259

164

AMBIGEDAD Y CONFLICTOS
Yacc detecta las ambigedades cuando intenta construir el parser. Cuando hay conflictos shift/reduce o reduce/reduce, produce el parser de todas formas. Lo hace seleccionando uno de los pasos vlidos cuando tenga que elegir, y para decidir qu eleccin hacer, utiliza las reglas para deshacer la ambigedad. Por defecto, estas reglas son dos: 1. En un conflicto shift/reduce, hace un desplazamiento. 2. En un conflicto reduce/reduce, reduce con la regla de la gramtica que aparezca primero en la especificacin de YACC (con esto se le da al usuario un control tosco del comportamiento del parser). Los conflictos pueden producirse por errores en la entrada o lgicos, o porque las reglas de la gramtica, aunque consistentes, requieren un parser ms complejo que el que Yacc puede construir. Adems pueden venir provocados por el uso de acciones dentro de las reglas, cuando deben ejecutarse antes de que el parser sepa qu regla aplicar. En estos casos, la aplicacin de reglas como las anteriores es inapropiada y conduce a analizadores incorrectos. En general, si al aplicarlas se llega a un analizador correcto, es posible reescribir la gramtica sin conflictos.

165

! Yacc siempre informa sobre el nmero de conflictos shift/reduce y reduce/reduce resueltos con las dos reglas anteriores. Para obtener ms informacin sobre los conflictos encontrados en las reglas por Yacc, si ejecutamos yacc con la opcin v, en el fichero y.output podemos encontrarla. Ejemplo.stat : | ; IF ( cond ) stat IF ( cond ) stat ELSE stat

En y.output tendramos: 23:shift/reduce conflict (shift 45, reduce 18) on ELSE state 23 stat : IF ( cond ) stat_ stat : IF ( cond ) stat_ELSE stat ELSE shift 45 . reduce 18 Con el ELSE hace un desplazamiento. En el estado 45 habr: stat : IF ( cond ) stat ELSE_stat

Si el smbolo de entrada no es ELSE, se reduce con la regla de la gramtica 18: stat : IF ( cond ) stat

! En este caso, la accin por defecto (shift) es la apropiada. En general, tenemos que comprobarlo.

166

ejem2.l %{ #include "y.tab.h" %} %% if p a "(" ")" else

return(IF); return(P); return(A); return(PARIZ); return(PARDE); return(ELSE);

ejem2.y %{ %} %start s %token %% s : IF PARIZ c PARDE s | IF PARIZ c PARDE s ELSE s | A ; c : P ; IF P A PARIZ PARDE ELSE

167

y.output
state 0 $accept : _s $end IF shift 2 A shift 3 . error s goto 1

state 1 $accept :

s_$end

$end accept . error state 2 s : s :

IF_PARIZ c PARDE s IF_PARIZ c PARDE s ELSE s

PARIZ shift 4 . error state 3 s : .

A_

(3)

reduce 3

state 4 s : s : P . c

IF PARIZ_c PARDE s IF PARIZ_c PARDE s ELSE s

shift 6 error goto 5

state 5 s : s :

IF PARIZ c_PARDE s IF PARIZ c_PARDE s ELSE s

PARDE shift 7 . error state 6 c : .

P_

(4)

reduce 4

168

state 7 s :
s : IF

IF PARIZ c PARDE_s
IF PARIZ c PARDE_s ELSE s shift 2

A . s

shift 3 error goto 8

8: shift/reduce conflict (shift 9, red'n 1) on ELSE state 8 s : IF PARIZ c PARDE s_ (1) s : IF PARIZ c PARDE s_ELSE s ELSE shift 9 . reduce 1 state 9 s :

IF PARIZ c PARDE s ELSE_s

IF shift 2 A shift 3 . error s goto 10

state 10 s : .

IF PARIZ c PARDE s ELSE s_

(2)

reduce 2

8/127 terminals, 2/600 nonterminals 5/300 grammar rules, 11/1000 states 1 shift/reduce, 0 reduce/reduce conflicts reported 5/601 working sets used memory: states,etc. 26/2000, parser 3/4000 6/3001 distinct lookahead sets 5 extra closures 10 shift entries, 1 exceptions 4 goto entries 0 entries saved by goto default Optimizer space used: input 28/2000, output 17/4000 17 table entries, 7 zero maximum spread: 262, maximum offset: 262

169

PRECEDENCIA Y ASOCIATIVIDAD
En el anlisis de expresiones aritmticas, las reglas anteriores para resolver conflictos no son suficientes. La mayora de las construcciones aritmticas usadas normalmente pueden describirse de forma natural mediante la nocin de niveles de precedencia para operadores, junto con la informacin sobre asociatividad izquierda o derecha. Resulta que gramticas ambiguas con reglas apropiadas para quitar la ambigedad pueden ser usadas para crear analizadores que son ms rpidos y ms fciles de escribir que los construidos para gramticas no ambiguas. La nocin bsica es escribir la gramtica con reglas de la forma: expr : expr OP expr y expr : UNARY expr para todos los operadores binarios y unarios deseados. Esto crea una gramtica muy ambigua, con muchos conflictos. Como reglas para quitar la ambigedad, el usuario especifica la precedencia de todos los operadores y la asociatividad de los operadores binarios. Esto es suficiente para permitir que Yacc genere un parser sin conflictos de acuerdo con esas reglas.

170

Las precedencias y asociatividades son ligadas a los tokens en la seccin de declaraciones. Esto se hace con una serie de lneas que comienzan con una palabra clave de Yacc: %left, %right, %nonassoc, seguidas de una lista de tokens. Los tokens en la misma lnea se asume que tienen la misma precedencia y asociatividad; las lneas son listadas en orden de precedencia creciente. %left describe asociatividad por la izquierda. %right describe asociatividad por la derecha. %nonassoc describe operadores que no pueden asociarse con ellos mismos. (Ej.: .LT. de FORTRAN, es decir, A .LT. B .LT. C es ilegal). ! Un token declarado con %left, %right y %nonassoc no necesita, pero puede, ser declarado tambin con %token. ! A veces un operador unario y un operador binario tienen la misma representacin simblica pero distintas precedencias (ej.: - como unario tiene ms precedencia que como binario). %prec cambia el nivel de precedencia asociado con una regla particular de la gramtica. Aparece inmediatamente despus que el cuerpo de la regla, antes de la accin o del punto y coma, y es seguido por un token.

171

Ejemplo.%right = %left + - %left * / %% expr : expr = expr | expr + expr | expr - expr | expr * expr | expr / expr | - expr %prec * | NAME ; + y - tienen asociatividad por la izquierda y menor precedencia que * y /, que tambin son asociativos por la izquierda. = es asociativo por la derecha y tiene la menor precedencia. La entrada a=b=c*d-e-f*g se estructurara a=(b=(((c*d)-e)-(f*g))). Por ltimo, el operador unario - tiene la misma precedencia que *.

172

Reglas para quitar la ambigedad usando precedencias y asociatividades para resolver conflictos: 1.- Las precedencias y asociatividades son guardadas para los tokens y literales que las tienen. 2.- A cada regla de la gramtica se le asocia una precedencia y una asociatividad: las del ltimo token o literal en el cuerpo de la regla. Si se usa %prec, la opcin por defecto se anula. Si el ltimo token (o el referenciado por %prec) no tiene dedinida precedencia o asociatividad, la regla tampoco.

3.- Cuando se produce un conflivto reduce/reduce o desplaza/reduce y el smbolo de entrada o la regla gramatical no tienen precedencia y asociatividad, se usan las dos reglas por defecto (ver transparencia 164). 4.- Si en un conflicto desplaza/reduce tanto el smbolo de entrada como la regla de produccin tienen precedencia y asociatividad, el conflicto se resuelve a favor de la accin (shift o reduce) asociada con la ms alta precedencia. Si tienen la misma precedencia, se usa la asociatividad; si es por la izquierda se reduce, por la derecha se desplaza, y la no asociatividad implica error. Los conflictos resueltos por precedencia y asociatividad no son tenidos en cuenta en el informe de conflictos de Yacc, y por tanto pueden ocultar errores. Hay que tener cuidado. Es bueno utilizar y.output.

173

MANEJO DE ERRORES
No es aceptable parar el procesamiento cuando se encuentra un error, sino que se debe seguir escaneando la entrada para encontrar ms errores sintcticos. Necesitamos volver a empezar a analizar despus de un error. Los algoritmos que hacen esto suelen decartar tokens de la cadena de entrada, e intentar ajustar ella analizador para que pueda continuar con la entrada. ! Para permitir al usuario algn control sobre este proceso, Yacc ofrece un hecho simple, perto razonablemente general: Se reserva para el manejo de errores el token de nombre "error". Puede ser usado en reglas gramaticales, cuando se espera un error y puede tener lugar una recuperacin. Se decide qu no-terminales "principales" tendrn recuperacin de errores asociados a ellos (expresiones, proposiciones, bloques, procedimientos,). Para cada uno de ellos se aade la produccin A error , siendo una cadena de smbolos gramaticales, a veces vaca. Yacc genera el analizador como si esas producciones fueran normales, pero cuando este analizador encuentra un error, trata a los estado cuyos conjuntos de elementos contengan producciones de error, de manera especial.

174

Al encontrar un error, Yacc extrae smbolos de la pila hasta que encuentra un estado donde el token "error" es legal, es decir, en l hay algo como: A _error Entonces desplaza un token ficticio error a la pila, como si lo hubiera encontrado en la entrada. Si es , se produce inmediatamente una reduccin a A, se invoca la accin semantica asociada a la produccin A error (rutinas de recuperacin de errores especificadas por el usuario, para intentar reinicializar tablas, etc.), y se eliminan smbolos de la entrada hasta que se encuentra uno con el que poder seguir el anlisis normal. Ej.: stat : error. Si no es vaca, salta la entrada buscando una subcadena que se pueda reducir a . Si slo consta de terminales, entonces busca esta subcadena en la entrada y la desplaza a la pila. Entonces reduce error a A, y prosigue el anlisis. Ej.: stat : error ';'. Para prevenir una cascada de errores, el analizador, despus de detectar un error, permanece en estado de error hasta que han sido ledos y deplazados con xito tres tokens. Si se detecta un error cuando est en estado de error, no se da mensaje, y se borra el token.

175

! Si no han sido especificadas reglas de error especiales, el procesamiento para cuando se detecta un error. ! Otra forma de regla de error se presenta en aplicaciones interactivas, dnde puede ser deseable permitir que vuelva a introducirse una lnea despus de un error. Ejemplo:
input: error '\n' {printf("vuelva a introducir lnea:");} input {$$=$4;}

Problema que puede surgir: Puesto que el parser debe procesar correctamente tres tokens de entrada antes de admitir que tiene resincronizada correctamente la entrada despus del error, puede ocurrir que en la lnea que ha vuelto a introducirse se produzca un error en los dos primeros tokens. En este caso borrara los tokens malos y no dara mensaje. Esto es inaceptable. Existe un mecanismo para forzar al analizador a creer que un error ha sido recuperado ya: yyerrork; que vuelve al parser a un modo normal. ! Despus del token "error", el siguiente token es aquel en que el error fue descubierto. A veces esto es inapropiado, por ejemplo, puede que una accin de recuperacin de errores se encargue de encontrar el lugar donde reanudar la entrada. En este caso, el token de anticipacin previo debe ser borrado. Esto puede hacerse por medio de yyclearin; Ejemplo.- stat : error {resynch(); yyerrork; yyclearin;};

176

EL ENTORNO YACC
La funcin producida por Yacc se llama yyparse(), y es de tipo entero. Cuando es llamada, ella, a su vez, va llamando a la funcin yylex() para obtener los tokens de entrada. Si detecta un error sin posibilidad de recuperacin, devuelve un 1; si el analizador lxico devuelve el final de fichero, el parser acepta y devuelve el valor 0. Para obtener un programa que funcione, es necesario un entorno que, o bien lo proporciona el usuario, o Yacc lo toma por defecto de las libreras (normalmente con -ly). Por ejemplo: main() debe ser definido, o bien se toma por defecto el siguiente: main() {return(yyparse());} yyerror, que imprime un mensaje de error cuando ocurre un error sintctico. Por defecto es:
yyerror(s) char *s; {fprintf(stderr,"%s\n",s);}

s es una cadena que contiene un mensaje de error. yychar es una variable que sirve para localizar el error. yydebug es una variable que normalmente est a 0. Si no es as, el parser dar informacin de sus acciones, smbolos de entrada ledos,. Puede servir para debugger.

177

CARACTERSTICAS AVANZADAS DE YACC


Simulacin de Error y Accept en las acciones YYACCEPT es una macro que hace que Yacc devuelva el valor 0. YYERROR hace que el parser se comporte como si el actual smbolo de entrada hubiera sido errneo. Con esta macro se llama a yyerror y tiene lugar una recuperacin de errores. Pueden ser usadas para simular analizadores sintcticos con mltiples marcadores de final de fichero o chequeo de sitaxis sensible al contexto. Acceso a valores desde las reglas Una accin puede referirse a valores devueltos por acciones a la izquierda de la regla actual. El mecanismo es el mismo que con acciones ordinarias, $i, pero en este caso i puede ser 0 o negativo. Ejemplo.sent : adj : | ; noun : | adj noun verb adj noun {} THE {$$=THE;} YOUNG {$$=YOUNG;}

DOG CRONE

{$$=DOG;} {(*)if($0==YOUNG) {printf("what?\n");} $$=CRONE;};

(*)

Chequea que el token precedente desplazado no fuera YOUNG. Hace falta conocer muy bien qu puede haber antes de noun.

178

Soporte para tipos de valores arbitrarios Por defecto, los valores devueltos por las acciones y el analizador lxico son enteros. Yacc tambin puede soportar valores de otros tipos, incluyendo estructuras. La pila de valores de Yacc se declara como una unin de los tipos de los valores deseados. El usuario declara la unin, y asocia los nombres de miembros de la unin a cada token y a cada no terminal que tenga un valor. Cuando el valor es referenciado con $$ o $n, Yacc insertar automticamente el tipo apropiado, de forma que no tendrn lugar conversiones inesperadas. Existen tres mecanismos para proporcionar esto: 1) Definicin de la unin. Debe hacerlo el usuario, puesto que otros programas como el analizador lxico deben conocer los nombres de los miembros de la unin. Puede hacerse de dos formas: a) Se pone en la seccin de declaraciones %union {cuerpo de la unin}

Declara la pila de valores Yacc, y las variables externas yylval e yyval, con tipos iguales a esta unin. Si se ejecuta Yacc con la opcin -d la declaracin de la unin se copia en y.tab.h. b)Se puede declarar la unin en un fichero cabecera, usando un typedef para definir la variable YYSTYPE: typedef union YYSTYPE; {cuerpo de la union}

Despus se incluira este fichero en la seccin de declaraciones con %{ y %}.

179

2) Forma de asociar un nombre de miembro de una unin con tokens y no terminales. La construccin <nombre> se usa para indicar el nombre de un miembro de una unin. a)Para asociar el tipo con los tokens, se pone <nombre> entre %token, %left, %right o %nonassoc y la lista de tokens listados. b)Para asocias el tipo con los no terminales, se pone <nombre> entre %type y la lista de no terminales. Ejemplo: %left %type <optype> '+' '-' <nodetype> expr stat

3) Mecanismo para decribir el tipo de aquellos pocos valores donde Yacc no puede determinar el tipo fcilmente. Por ejemplo, cuando hay una accin dentro de una regla, esta accin no tiene tipo a priori, o cuando se referencian valores del contexto de la izquierda (ver transparencia 177). En estos casos, un tipo puede ser impuesto en una referencia, insertando <nombre> inmediatamente despus del primer $. Ejemplo.rule : aaa {$<intval>$=3;} bbb {fun($<intval>2,$<other>0);}

180

Ejemplo.- Especificacin Yacc para una pequea calculadora:


%{ #include <stdio.h> #include <ctype.h> int regs[26]; int base; %} %start list %token DIGIT LETTER %left '|' %left '&' %left '+' '-' %left '*' '/' '%' %left UMINUS /* supplies precedence for unary minus */ %% /* beginning of rules section */ list : /* empty */ | list stat '\n' | list error '\n' {yyerrork;} ; stat : expr {printf("%d\n",$1);} | LETTER '=' expr {regs[$1]=$3;} ; expr : '(' expr ')' {$$=$2;} | expr '+' expr {$$=$1 + $3;} | expr '-' expr {$$=$1 - $3;} | expr '*' expr {$$=$1 * $3;} | expr '/' expr {$$=$1 / $3;} | expr '%' expr {$$=$1 % $3;} | expr '&' expr {$$=$1 & $3;} | expr '|' expr {$$=$1 | $3;} | '-' expr %prec UMINUS {$$= - $2;} | LETTER {$$=regs[$1];} | number ;

181

number : | ; %%

DIGIT {$$=$1; base= ($1==0) ? 8 : 10;} number DIGIT {$$=base*$1+$2;}

/* start of programs */

yylex() { /*lexical analysis routine */ /* returns LETTER for a lower case letter, yylval=0 through 25 */ /* return DIGIT for a digit, yylval=0 through 9 */ /* all other character are returned immediately */ int c; while( (c=getchar())==' ') {/* skip blanks */} /* c is now nonblank */ if ( islower(c)){ yylval=c-'a'; return(LETTER); } if (isdigit(c)){ yylval=c-'0'; return(DIGIT); } return(c); }

S-ar putea să vă placă și