Sunteți pe pagina 1din 177

A AN N L LI IS SI IS S S SI IN NT T C CT TI IC CO O

E EN N
P PR RO OC CE ES SA AD DO OR RE ES S D DE E L LE EN NG GU UA AJ JE E

Apuntes de Procesadores de Lenguaje


M. Cndida Luengo Dez
Juan Manuel Cueva Lovelle
Francisco Ortn Soler
Ral Izquierdo Castanedo
Aquilino A. Juan Fuente
Jose Emilio Labra Gayo



Oviedo, Enero 2005


ii

i

Tabla de Contenidos
TEMA 4 FUNDAMENTOS DEL ANLISIS SINTCTICO............................... 1
4.1 INTRODUCCIN......................................................................................................................... 1
4.2 ESPECIFICACIN SINTCTICA DE LOS LENGUAJES DE PROGRAMACIN................. 2
4.2.1 Derivaciones y rboles sintcticos............................................................................. 3
4.2.1.1 Ejemplo: gramtica de expresiones aritmticas .................................................. 3
4.2.2 Derivaciones ms a la izquierda y ms a la derecha.................................................. 4
4.2.2.1 Ejemplo ............................................................................................................... 4
4.2.3 Capacidad de descripcin de las gramticas de tipo 2............................................... 6
4.2.4 Recursividad .............................................................................................................. 7
4.2.4.1 Ejemplo ............................................................................................................... 7
4.2.5 Gramticas ambiguas................................................................................................. 8
4.2.5.1 Ejemplo ............................................................................................................... 8
4.2.6 Conversin de gramticas ambiguas en no ambiguas................................................ 9
4.2.6.1 Ejemplo: representacin de los arrays y llamada de funciones........................... 9
4.2.6.2 Ejemplo: else danzante o ambiguo...................................................................... 9
4.2.6.3 Ejemplo: definicin de precedencia y asociatividad ......................................... 11
4.2.7 Gramticas limpias y gramticas sucias .................................................................. 14
4.2.8 Limpieza de gramticas ........................................................................................... 14
4.2.8.1 Teorema de los smbolos vivos ......................................................................... 15
4.2.8.2 Ejemplo ............................................................................................................. 15
4.2.8.3 Teorema de los smbolos accesibles.................................................................. 16
4.2.8.4 Ejemplo ............................................................................................................. 16
4.2.8.5 Anlisis automtico de la limpieza de gramticas............................................. 16
4.3 FORMAS NORMALES DE CHOMSKY Y GREIBACH.......................................................... 17
4.3.1 Forma Normal de Chomsky (FNC) ......................................................................... 17
4.3.1.1 Teorema de la forma normal de Chomsky ........................................................ 17
4.3.1.2 Ejemplo ............................................................................................................. 18
4.3.2 Forma Normal de Greibach (FNG).......................................................................... 19
4.3.2.1 Teorema de la forma normal de Greibach......................................................... 19
4.3.2.2 Ejemplo ............................................................................................................. 19
4.4 TIPOS GENERALES DE ANALIZADORES SINTCTICOS ................................................. 20
4.5 SINTAXIS CONCRETA Y SINTAXIS ABSTRACTA............................................................. 21
TEMA 5 ANLISIS SINTCTICO DESCENDENTE .................................... 23
5.1 INTRODUCCIN....................................................................................................................... 23
5.2 EL PROBLEMA DEL RETROCESO..................................................................................................... 24
5.2.1 Ejemplo de retroceso ............................................................................................... 24
5.3 ANLISIS SINTCTICO DESCENDENTE CON RETROCESO................................................................ 27
5.3.1 Algoritmo de anlisis sintctico descendente con retroceso.................................... 27
5.3.2 Corolario.................................................................................................................. 27

ii
5.4 ANLISIS DESCENDENTE SIN RETROCESO ..................................................................................... 28
5.4.1 Gramticas LL(k).....................................................................................................28
5.4.1.1 Teorema.............................................................................................................28
5.4.1.2 S-gramticas ......................................................................................................28
5.4.1.3 Corolario de la definicin de S-gramticas .......................................................29
5.4.1.4 Conjunto de smbolos INICIALES o cabecera..................................................30
5.4.1.5 Gramticas LL(1) simples .................................................................................31
5.4.1.6 Corolario de las gramticas LL(1) simples .......................................................31
5.4.1.7 Teorema de equivalencia entre gramticas LL(1) y S-gramticas ....................31
5.4.1.8 Conjunto de smbolos SEGUIDORES o siguientes ..........................................32
5.4.1.9 Conjunto de smbolos DIRECTORES ..............................................................34
5.4.1.10 Definicin de las gramticas LL(1) .............................................................34
5.4.2 Condiciones de las gramticas LL(1).......................................................................35
5.4.2.1 Primera condicin de Knuth..............................................................................35
5.4.2.2 Segunda condicin de Knuth.............................................................................35
5.4.2.3 Tercera condicin de Knuth ..............................................................................35
5.4.2.4 Cuarta condicin de Knuth................................................................................36
5.4.3 Transformacin de gramticas.................................................................................36
5.4.3.1 La recursividad a izquierdas..............................................................................36
5.4.3.2 Eliminacin de la recursividad a izquierdas......................................................38
5.4.3.3 Factorizacin y sustitucin................................................................................41
5.4.3.4 Transformacin de gramticas mediante aspectos semnticos..........................44
5.5 CONSTRUCCIN DE ANALIZADORES SINTCTICOS DESCENDENTES............................................... 45
5.5.1 Mtodos basados directamente en la sintaxis...........................................................45
5.5.1.1 Reglas de construccin de diagramas sintcticos..............................................45
5.5.1.2 Traduccin de reglas sintcticas a programas ...................................................46
5.5.2 Analizadores sintcticos recursivos descendentes ...................................................50
5.5.3 Analizadores sintacticos descendentes dirigidos por tabla ......................................53
5.5.4 Construccin de analizadores sintcticos decendentes basados en autmatas de pila
..................................................................................................................................55
5.5.5 Analizadores sintcticos descendentes deterministas dirigidos por estructuras de
datos .........................................................................................................................56
5.5.5.1 Traduccin de reglas sintcticas a estructuras de datos. Ejemplo: ....................58
5.5.5.2 Construccin de analizadores sintcticos descendentes genricos dirigidos por
estructuras de datos ...........................................................................................58
5.6 TRATAMIENTO DE ERRORES SINTCTICOS .................................................................................... 58
TEMA 6 ANLISIS SINTCTICO ASCENDENTE....................................... 63
6.1 INTRODUCCIN....................................................................................................................... 63
6.2 ANALIZADORES LR...................................................................................................................... 63
6.2.1 Ejemplo de anlisis ascendente con retroceso .........................................................63
6.3 GRAMTICAS Y ANALIZADORES LR(K) ........................................................................................ 65
6.4 ESTRUCTURA Y FUNCIONAMIENTO DE UN ANALIZADOR LR ......................................................... 65
6.4.1 Ejemplo....................................................................................................................67
6.5 ALGORITMO DE ANLISIS LR ....................................................................................................... 68
6.6 GRAMTICAS LR(K) - LL(K)........................................................................................................ 69
6.7 TRATAMIENTO DE ERRORES SINTCTICOS ................................................................................... 70
6.8 GENERADORES DE PROCESADORES DE LENGUAJES...................................................................... 71

iii
6.8.1 Estructura................................................................................................................. 71
6.8.2 Caractersticas.......................................................................................................... 72
6.8.3 Clasificacin ............................................................................................................ 73
6.8.4 Ventajas ................................................................................................................... 74
6.8.5 Generadores de analizadores sintcticos.................................................................. 75
6.8.5.1 Lex/Yacc ........................................................................................................... 76
6.8.5.2 Antlr ................................................................................................................ 129
6.8.5.3 JavaCC ............................................................................................................ 137
ANEXO I: ALGORITMO DE DECISIN ........................................................... 141
PASO 1: Encontrar los smbolos no terminales y producciones anulables................ 141
PASO 2: Construccin de la relacin EMPIEZA DIRECTAMENTE CON............. 144
PASO 3: Construccin de la relacin EMPIEZA CON............................................. 146
PASO 4: Clculo del conjunto de smbolos INICIALES de cada no terminal .......... 147
PASO 5: Clculo de los conjuntos INICIALES de cada produccin......................... 148
PASO 6: Construccin de la relacin ESTA SEGUIDO DIRECTAMENTE POR... 148
PASO 7: Construccin de la relacin ES FIN DIRECTO DE ................................... 150
PASO 8: Construccin de la relacin ES FIN DE...................................................... 152
PASO 9: Construccin de la relacin ESTA SEGUIDO POR................................... 152
PASO 10: Calcular el conjunto de seguidores de cada no terminal anulable............. 153
PASO 11: Calcular el conjunto de smbolos directores ............................................. 153
ANEXO II: ANALIZADOR SINTCTICO GENERADO CON YACC............ 153
EJERCICIOS PROPUESTOS................................................................................. 166
BIBLIOGRAFIA....................................................................................................... 169

Anlisis Sintctico en Procesadores de Lenguaje
1

TEMA 4 FUNDAMENTOS DEL ANLISIS SINTCTICO


4.1 INTRODUCCIN
El anlisis sintctico (parser en lengua inglesa) toma los tokens que le enva el
analizador lxico y comprueba si con ellos se puede formar alguna sentencia vlida del
lenguaje. Recurdese que se entiende por sintaxis como el conjunto de reglas formales
que especifican como se construyen las sentencias de un determinado lenguaje.
La sintaxis de los lenguajes de programacin habitualmente se describe mediante
gramticas libres de contexto (o gramticas tipo 2), G = { S, V
T,
V
NT,
P } donde:
S: el axioma o smbolo inicial
V
T
: conjunto de terminales, los componentes lxicos o tokens
V
NT
: conjunto de no-terminales
P: conjunto de reglas de produccin de la forma:
V
NT
X
1
, ... , X
n
con X
i
(V
T
V
NT
)
*

utilizando para ello algn tipo de notacin como por ejemplo, las notaciones BNF y
EBNF. Esta especificacin ofrece numerosas ventajas:
Una gramtica da una especificacin sintctica precisa, y fcil de comprender,
de un lenguaje de programacin.
Para ciertas clases de gramticas se puede construir automticamente un
analizador sintctico, que determina si un programa est bien construido.
Como beneficio adicional, el proceso de construccin del analizador sintctico
puede revelar ambigedades sintcticas y otras dificultades para la
construccin del parser que de otra forma no sern detectadas en la fase de
diseo inicial de un lenguaje y que podran se arrastradas a su compilador.
Una gramtica bien diseada da una estructura al lenguaje de programacin
que se utiliza en la traduccin del programa fuente en cdigo objeto correcto y
para la deteccin de errores. Existen herramientas que a partir de la
descripcin de la gramtica se puede generar el cdigo del analizador
sintctico.
Los lenguajes de programacin despus de un periodo de tiempo, adquieren
nuevas construcciones y llevan a cabo tareas adicionales. Estas nuevas
construcciones se pueden aadir ms fcilmente al lenguaje cuando hay una
implementacin basada en una descripcin gramatical del lenguaje.

Anlisis Sintctico en Procesadores de Lenguaje
2
Funciones del analizador sintctico:
Comprobar si la cadena de tokens proporcionada por el analizador lxico
puede ser generada por la gramtica que define el lenguaje fuente (gramtica
libre del contexto, GLC).
Construir el rbol de anlisis sintctico que define la estructura jerrquica de
un programa y obtener la serie de derivaciones para generar la cadena de
tokens. El rbol sintctico se utilizar como representacin intermedia en la
generacin de cdigo.
Informar de los errores sintcticos de forma precisa y significativa. Deber
estar dotado de un mecanismo de recuperacin de errores para continuar con el
anlisis.
El papel del analizador sintctico dentro de un procesador de lenguaje puede
representarse segn el esquema de la figura 1.
Tabla
de
smbolos
Gramtica Libre de Contexto
Analizador
lxico
Analizador
sintctico
Analizador
semntico
token
pide
siguiente
token
representacin
intermedia
rbol
sintctico
rograma
fuente
Gestor de
errores

Figura 1: Interfaces del analizador sintctico
El anlisis sintctico se puede considerar como una funcin que toma como entrada la
secuencia de componentes lxicos (tokens) producida por el anlisis lxico y produce
como salida el rbol sintctico. En realidad, el anlisis sintctico hace una peticin al
anlisis lxico del token siguiente en la entrada (los smbolos terminales) conforme lo va
necesitando en el proceso de anlisis.
4.2 ESPECIFICACIN SINTCTICA DE LOS LENGUAJES DE
PROGRAMACIN
Habitualmente la estructura sintctica de los lenguajes de programacin se especifica
mediante Gramticas Libres de Contexto (GLC).
Pregunta: Si las expresiones regulares son un caso particular de GLC, por qu no se
incluye la especificacin lxica como parte de la sintaxis?
Respuesta: Los AFD son muy sencillos de implementar y son muy eficientes frente a
los autmatas de pila necesarios para reconocer las GLC, la eficiencia del procesador de
lenguaje se vera comprometida.
Anlisis Sintctico en Procesadores de Lenguaje
3
4.2.1 Derivaciones y rboles sintcticos
Analizar sintcticamente una cadena de tokens es encontrar para ella un rbol
sintctico o derivacin, que tenga como raz el smbolo inicial de la gramtica libre de
contexto y mediante la aplicacin sucesiva de sus reglas de derivacin se pueda alcanzar
dicha cadena como hojas del rbol sintctico. En caso de xito la sentencia pertenece al
lenguaje generado por la gramtica, y puede proseguirse el proceso de compilacin. En
caso contrario, es decir cuando no se encuentra el rbol que genera dicha cadena, se dice
entonces que la sentencia no pertenece al lenguaje, y el compilador emite un mensaje de
error, pero el proceso de compilacin trata de continuar. Algunos compiladores paran el
proceso de compilacin cada vez que encuentran un error, en ellos es necesario realizar
tantas compilaciones como errores tendra el programa fuente.
4.2.1.1 Ejemplo: gramtica de expresiones aritmticas
Supongamos que se desea construir una gramtica G = {VN, VT, S, P} que describe
un lenguaje basado en la utilizacin de expresiones aritmticas con sumas, diferencias,
productos, divisiones, potenciacin, parntesis, y menos unario. Este lenguaje tambin
permitir el uso de constantes e identificadores de variables. La gramtica est
compuesta por un vocabulario terminal VT formado por los smbolos de los operadores,
parntesis y los tokens constante e identificador. El vocabulario no terminal VN slo
contendr el smbolo no terminal <EXP>. El smbolo inicial S tambin ser <EXP>. La
gramtica G queda tal y como se muestra a continuacin:
VT = {+, *, /, ^, (, ), -, identificador, constante}
VN = { <EXP> }
S = <EXP>
Las reglas de produccin P que en un principio representaran al lenguaje se muestran
a continuacin numeradas del (1) al (9). La numeracin se utilizar para indicar la regla
que se aplica en cada derivacin.
(1) <EXP> ::= <EXP> + <EXP>
(2) <EXP> ::= <EXP> * <EXP>
(3) <EXP> ::= <EXP> - <EXP>
(4) <EXP> ::= <EXP> / <EXP>
(5) <EXP> ::= <EXP> ^ <EXP>
(6) <EXP> ::= - <EXP>
(7) <EXP> ::= ( <EXP> )
(8) <EXP> ::= identificador
(9) <EXP> ::= constante
Sea la expresin - ( a * 9 ) se desea saber si pertenece o no al lenguaje, para lo
cual se pretende alcanzar a partir de derivaciones desde el smbolo inicial de la gramtica,
construyndose el rbol de anlisis sintctico de la figura 2. El rbol sintctico no indica
el orden seguido para alcanzar la cadena incgnita. Para indicar el orden de aplicacin de
las derivaciones con las que se construy el rbol se muestran las derivaciones siguientes,
sealando encima de la flecha el nmero de la regla aplicada:
Anlisis Sintctico en Procesadores de Lenguaje
4
<EXP>
(6)
-<EXP>
(7)
-(<EXP
(2)
-(<EXP> * <EXP
(8)
-( identificador
* <EXP>)
(9)
-(identificador * constante)
El analizador lxico se encarga de reconocer los smbolos terminales, as tambin
indica que a es un identificador y que 9 es una constante.
<EXP>
<EXP>
( <EXP> )
<EXP> * <EXP>
identificador
a
constante
9

Figura 2: rbol de anlisis sintctico de -(a*9)
4.2.2 Derivaciones ms a la izquierda y ms a la derecha
Las reglas de derivacin que se aplican desde el smbolo inicial hasta alcanzar la
sentencia a reconocer, pueden reemplazar los smbolos no terminales tomando el de ms a
la izquierda (derivaciones ms a la izquierda) o tomando el no terminal de ms a la
derecha (derivaciones ms a la derecha). A estos dos tipos de derivaciones se les llama
derivaciones cannicas.
Habitualmente se trabaja con derivaciones ms a la izquierda.
Se puede demostrar que todo rbol de anlisis sintctico tiene asociado una nica
derivacin izquierda y una nica derivacin derecha. Sin embargo una sentencia de un
lenguaje puede dar lugar a ms de un rbol de anlisis sintctico tanto por la derecha
como por la izquierda.
4.2.2.1 Ejemplo
Sea la gramtica de contexto libre G = (VN, VT, S, P) para al generacin de
expresiones aritmticas donde VN = {S, T, F}, VT = {a, b, +, *, (, )} y las
reglas de produccin P son:
(1) S S + T
(2) S T
(3) T T* F
(4) T F
(5) F (S)
(6) F a
(7) F b
Anlisis Sintctico en Procesadores de Lenguaje
5
Determinar los rboles sintcticos ms a la izquierda y ms a la derecha que reconocen
la cadena a*(a+b).
Para determinar el rbol ms a la izquierda se parte del smbolo inicial S y en la
variable parse se van poniendo las producciones aplicadas, utilizando nmeros para
indicar los cdigos de cada regla aplicada. Siempre se expansiona el no terminal ms a la
izquierda en el rbol hasta alcanzar un smbolo terminal. En las figuras siguientes se
observa como se alcanza el rbol ms a la izquierda aplicando las producciones por el
orden indicado por los nmeros 23465124647.
parse= 2
parse= 23
parse= 234
S
T
S
T
T * F
S
T
T * F
F

parse= 234561
S
T
T * F
F
a
( S )
S + T
parse= 23465
S
T
T * F
F
a
( S )
parse= 2346
S
T
T * F
F
a


S
T
T * F
F
a
( S )
S + T
T
parse= 2346512
S
T
T * F
F
a
( S )
S + T
T
F
parse= 23465124
S
T
T * F
F
a
( S )
S + T
T
F
a
parse= 234651246
S
T
T * F
F
a
( S )
S + T
T
F
a
F
parse= 2346512464
S
T
T * F
F
a
( S )
S + T
T
F
a
F
b
parse Izdo= 23465124647

Para obtener el rbol ms a la derecha se parte del smbolo inicial S y se van
aplicando las producciones de forma que se eligen en primer lugar las expansiones del
smbolo no terminal ms a la derecha en el rbol. As en la variable produccin utilizada
(p.u.) se van colocando los nmeros de las producciones utilizadas. En las figuras
siguientes puede observarse el orden de sustitucin hasta alcanzar el rbol ms a la
derecha representado por los nmeros 23514724646.
Anlisis Sintctico en Procesadores de Lenguaje
6
p.u.= 235
S
T
T * F
( S )
p.u.= 23
S
T
T * F
produccin
S
T
utilizada= 2

S
T
T * F
( S )
S + T
F
b
p.u.= 235147
S
T
T * F
( S )
S + T
F
p.u.= 23514
S
T
T * F
( S )
S + T
p.u.= 2351

S
T
T * F
( S )
S + T
T
F
F
b
p.u.= 23514724
S
T
* F
( S )
S + T
T F
b
p.u.= 2351472
S
T
T * F
( S )
S + T
T
F
a
F
b
p.u.= 235147246
S
T
T * F
F
a
( S )
S + T
T
F
a
F
b
p.u.= 23514724646
S
T
T * F
F ( S )
S + T
T
F
a
F
b
p.u.= 2351472464


Puede observarse que los rboles finales alcanzados son los mismos aunque por
distintos caminos. En general puede ocurrir que el rbol ms a la izquierda y el rbol ms
a la derecha no sea el mismo, dando lugar a problemas de ambigedad tal y como se
estudia en el apartado 4.2.4.
4.2.3 Capacidad de descripcin de las gramticas de tipo 2
La mayora de las construcciones de los lenguajes de programacin implican
recursividad y anidamientos y las gramticas libres de contexto pueden representar este
tipo de especificaciones, sin embrago, existen ciertos aspectos de los lenguajes de
Anlisis Sintctico en Procesadores de Lenguaje
7
programacin que no se pueden representar. A estos aspectos se les llama aspectos
semnticos, y son aspectos en general dependientes del contexto. Por ejemplo, el
problema de declaracin de los identificadores antes de su uso.
Por este motivo, la definicin sintctica de los lenguajes de programacin viene dada
habitualmente en dos partes. La mayor parte de la sintaxis se describe por gramticas
libres de contexto, especificadas en notacin BNF o EBNF y el resto de la sintaxis, que
no se puede describir por medio de gramticas libres de contexto, se presenta como un
aadido en lenguaje natural, y se trata en el compilador en la parte de anlisis semntico.
Tambin se amplan las gramticas libres de contexto con atributos, condiciones y reglas
semnticas, con el objeto de definir las especificaciones semnticas.
Ejemplo: llamada a funciones
En la llamada a subrutinas se ha de comprobar que el nmero de argumentos coincida
con el nmero de parmetros ficticios. Esto no lo pueden describir las gramticas libres
de contexto. Esta verificacin se suele hacer en la fase de anlisis semntico. Otra
solucin es la que utiliza el lenguaje C ANSI obligando a utilizar la definicin de
funciones prototipo.
4.2.4 Recursividad
Las reglas de produccin de una gramtica estn definidas de forma que al realizar
sustituciones dan lugar a recursividades. Entonces se dice que una regla de derivacin es
recursiva si es de la forma:
A A
Si es nula, es decir A A se dice que la regla de derivacin es recursiva a
izquierda. De la misma forma A A es recursiva a derecha (vase figura 3).
Recursividad a Izquierda
A
A
A
A

Recursividad a Derecha
A
A
A
A

Figura 3: Recursividad a izquierdas y a derechas
Los analizadores sintcticos que trabajan con derivaciones ms a la izquierda deben
evitar las reglas de derivacin recursivas a izquierda, pues entraran en un bucle infinito.
4.2.4.1 Ejemplo
Sea la gramtica siguiente que describe nmeros enteros e identificadores:
Anlisis Sintctico en Procesadores de Lenguaje
8
<entero> <dgito> | <entero> <dgito>
<identificador> <letra> | <identificador> <letra> |
<identificador> <dgito>
sus rboles sintcticos son los de la figura 4.
<entero>
<entero>
<entero>
<entero>
<entero>
<dgito>
<dgito>
<dgito>
<dgito>
dgito>
<identificador>
<identificador>
<identificador>
<identificador>
<identificador>
<letra>
<letra>
<letra>
<dgito>
<letra>
<letra>

Figura 4: rboles sintcticos recursivos
4.2.5 Gramticas ambiguas
Una sentencia generada por una gramtica es ambigua si existe ms de un rbol
sintctico para ella. Una gramtica es ambigua si genera al menos una sentencia
ambigua, en caso contrario es no ambigua. Ntese que se llama a la gramtica ambigua y
no al lenguaje que describe dicha gramtica. Hay muchas gramticas equivalentes que
pueden generar el mismo lenguaje, algunas son ambiguas y otras no. Sin embargo existen
ciertos lenguajes para los cuales no pueden encontrarse gramticas no ambiguas. A tales
lenguajes se les denomina ambiguos intrnsecos. Por ejemplo el lenguaje {x
i
, y
j
, z
k
/ i=j o
j=k} es un lenguaje ambiguo intrnseco.
El grado de ambigedad de una sentencia se caracteriza por el nmero de rboles
sintcticos distintos que la generan.
La ambigedad de una gramtica es una propiedad indecidible, lo que significa que no
existe ningn algoritmo que acepte una gramtica y determine con certeza y en un tiempo
finito si la gramtica es ambigua o no.
4.2.5.1 Ejemplo
Sea la gramtica de manejo de expresiones mostrada en el apartado 4.2.1.1.Se puede
comprobar que la sentencia: 5-C*6 tiene dos derivaciones ms a la izquierda diferentes, y
cuyos rboles
EXP> <EXP>-<EXP>
constante-<EXP>
5-<EXP>*<EXP>
5-dentificador*<EXP>
5-C*constante
5-C*6
<EXP> <EXP>*<EXP>
<EXP>-<EXP>*<EXP>
constante-<EXP>*<EXP>
5-identificador*<EXP>
5-C*constante
5-C*6

Anlisis Sintctico en Procesadores de Lenguaje
9
EXP
EXP EXP -
EXP EXP *
identificador constante
c 6
constante
5

EXP
EXP EXP *
EXP EXP -
identificador constante
5 c
constante
6

Figura 5: rbol 1 Figura 6: rbol 2
El rbol de anlisis sintctico de la figura 5 refleja la precedencia habitual de
matemticas de la operacin sustraccin sobre la multiplicacin. Mientras que en el rbol
de la figura 6 no se respeta esta precedencia.
4.2.6 Conversin de gramticas ambiguas en no ambiguas
Se ha indicado anteriormente que la propiedad de ambigedad es indecidible, es decir,
no existe ni puede construirse un algoritmo que, tomando una gramtica en BNF,
determine con certeza, y en plazo finito de tiempo, si es ambigua o no. Sin embargo, se
han desarrollado las condiciones que, de cumplirse, garantizan la no ambigedad,
aunque en caso de que no se cumplan no puede afirmarse nada. Son condiciones
suficientes pero no necesarias. As, las gramticas del tipo LL(k) y LR(k), que se
estudiarn en otros apartados siguientes a ste, no son ambiguas, pero una gramtica que
no sea LL(k) ni LR(k) no puede decirse que sea ambigua.
Una gramtica ambigua produce problemas al analizador sintctico, por lo que interesa
construir gramticas no ambiguas.
Sin embargo, en algunos lenguajes de programacin, la ambigedad sintctica existe
y se deber eliminar con consideraciones semnticas.
4.2.6.1 Ejemplo: representacin de los arrays y llamada de funciones
La representacin de los elementos de un array puede dar lugar a ambigedades, pues
se puede confundir la llamada a una funcin con un elemento de un array. As la
expresin M(I,J,K) es ambigua, puede considerarse como el elemento M
i,j,k
del array, y
tambin la llamada a la subrutina M con los parmetros I,J,K.
Los lenguajes Pascal, C, C++, y Algol 60 resuelven esta ambigedad empleando
corchetes para representar los arrays.
El lenguaje FORTRAN no tiene declaracin obligatoria de variables ni de subrutinas,
pero resuelve la ambigedad obligando a declarar las funciones no estndar. La llamada a
subrutinas no constituye ambigedad pues se deben hacer con la sentencia CALL. El
lenguaje PL/I resuelve la ambigedad con la declaracin obligatoria de todas las
variables.
4.2.6.2 Ejemplo: else danzante o ambiguo
Una sentencia alternativa simple es de la forma siguiente:
Anlisis Sintctico en Procesadores de Lenguaje
10
<sentencia> ::= if <expresin> then <sentencia>
| if <expresin> then <sentencia> else <sentencia>
| otra_sentencia
Puede darse el caso de que la sentencia que va despus del then sea otra sentencia
alternativa, es decir se produce una sentencia if-then-else anidada de la forma:
if E
1
then if E
2
then S
1
else S
2

En este caso se produce una ambigedad pues la parte correspondiente al else puede
asociarse a dos if, lo que da lugar a dos rboles sintcticos, que se muestran en las
figuras 7 y 8.
<sentencia>
<sentencia>
IF E1 THEN
IF E2 THEN S1 ELSE S2

Figura 7: rbol 1, el else se asocia con el if anterior ms cercano
<sentencia>
IF E1 THEN IF E2 THEN S1 ELSE S2
<sentencia>

Figura 8: rbol 2, el else se asocia con el primer if
La mayor parte de los lenguajes de programacin con sentencias condicionales de este
tipo utilizan el primer rbol sintctico (Fig. 7), es decir utilizan la regla el else se
empareja con el if anterior ms cercano. Si se quisiera asociar la parte else con la
sentencia if anterior se deber recurrir al uso de palabras reservadas, llaves, etc.
Ejemplo:
if (x != 0)
{if (y == 1/x) ok = TRUE;}
else z = 1/x;
Esta regla elimina la ambigedad y se puede incorporar a la gramtica. Se debe, por
tanto, construir una gramtica equivalente que elimine dicha ambigedad. La idea es que
una sentencia que aparezca entre un tTHEN y un ELSE debe estar emparejada, es decir no
debe terminar con un THEN sin emparejar seguido de cualquier sentencia, porque
entonces el ELSE estara obligado a concordar con este THEN no emparejado. As
sentencia emparejada tan solo puede ser una sentencia alternativa IF-THEN-ELSE que no
contenga sentencias sin emparejar o cualquier otro tipo de sentencia diferente de la
alternativa. La nueva gramtica equivalente ser la siguiente:

Anlisis Sintctico en Procesadores de Lenguaje
11
<sentencia> <sent_emparejada> | <sent_no_emparejada>
<sent_emparejada> if <exp> then <sent-emparejada> else <sent_emparejada>
| otra_sentencia
<sent_no_emparejada> if <exp> then <sentencia>
| if <exp> then <sent_emparejada> else <sent_no_emparejada>
<exp> 0 | 1
4.2.6.3 Ejemplo: definicin de precedencia y asociatividad
Una de las principales formas de evitar la ambigedad es definiendo la precedencia y
asociatividad de los operadores.
As el lenguaje C est basado en el uso intensivo de operadores, con una definicin
estricta de las precedencias y asociatividades de cada uno de ellos. Volvamos otra vez a la
gramtica de expresiones ya utilizada en el ejemplo 4.2.1.1. Dicha gramtica era ambigua
debido a problemas de precedencia y asociatividad de sus operadores. En este ejemplo se
mostrar como se refleja la precedencia y asociatividad en la gramtica para evitar la
ambigedad.
Se define el orden de precedencia de evaluacin de las expresiones y los
operadores. A continuacin se presenta el orden de precedencia de mayor a
menor precedencia:
1) ( ) identificadores constantes
2) - (menos unario)
3) ^ (potenciacin)
4) * /
5) + -
La asociatividad se define de forma diferente para el operador potenciacin
que para el resto de los operadores. As el operador ^ es asociativo de derecha
a izquierda:
a ^ b ^ c = a ^ (b ^ c)
mientras que el resto de los operadores binarios sern asociativos de izquierda a
derecha, si hay casos de igual precedencia.
a-b-c= (a-b)-c
a+b-c= (a+b)-c
a*b/c= (a*b)/c
Estas dos propiedades, precedencia y asociatividad, son suficientes para convertir la
gramtica ambigua basada en operadores en no ambigua, es decir, que cada sentencia
tenga slo un rbol sintctico.
Anlisis Sintctico en Procesadores de Lenguaje
12
Para introducir las propiedades anteriores de las operaciones en la gramtica se tiene
que escribir otra gramtica equivalente en la cual se introduce un smbolo no terminal
por cada nivel de precedencia.
4.2.6.3.1 Nivel de precedencia 1
Se introduce el no terminal <ELEMENTO> para describir una expresin indivisible, y
con mxima precedencia, por lo tanto
<ELEMENTO> ::= ( <EXP> ) | identificador | constante
4.2.6.3.2 Nivel de precedencia 2
Se introduce el no terminal <PRIMARIO>, que describe el menos unario y el no
terminal de precedencia superior.
<PRIMARIO> ::= - <PRIMARIO> | <ELEMENTO>
4.2.6.3.3 Nivel de precedencia 3
El siguiente no terminal que se introduce es <FACTOR> que describe los operadores
del siguiente nivel de precedencia.
<FACTOR> ::= <PRIMARIO> ^ <FACTOR> | <PRIMARIO>
Hay que sealar que el orden es fundamental. As <PRIMARIO> ^ <FACTOR>
obliga a expresiones del tipo a ^ b ^ c a agruparse como a ^ (b ^ c) siendo su rbol
sintctico el representado en la figura 9.
<FACTOR>
<PRIMARIO> ^ <FACTOR>
<ELEMENTO>
identificador
a
<PRIMARIO> ^ <FACTOR>
<ELEMENTO>
identificador
b
<PRIMARIO>
<ELEMENTO>
identificador
c

Figura 9: rbol de anlisis sintctico de a^b^c

Anlisis Sintctico en Procesadores de Lenguaje
13
4.2.6.3.4 Nivel de precedencia 4
El siguiente no terminal que se introduce es <TERMINO>, que es una secuencia de
uno o ms factores conectados con operadores de nivel de precedencia 4, es decir:
multiplicacin y divisin.
<TERMINO> ::= <TERMINO> * <FACTOR> |
<TERMINO> / <FACTOR> |
<FACTOR>
El orden de esta produccin obliga a que expresiones del tipo:
a * b / c signifique (a * b) / c
4.2.6.3.5 Nivel de precedencia 5
Por ltimo se introduce el no terminal <EXP> que representar a <TERMINO>
conectado a otro <TERMINO> con operadores de menor precedencia: adicin y
sustraccin.
<EXP> ::= <EXP> + <TERMINO> |
<EXP> - <TERMINO> |
<TERMINO>
4.2.6.3.6 Gramtica equivalente no ambigua
Entonces la nueva gramtica no ambigua ser GNA= (VN, VT, S, P) donde:
VN= {<ELEMENTO>, <PRIMARIO>, <FACTOR>, <TERMINO>, <EXP>}
VT= {identificador, constante, (, ), ^, *, /, +, -}
S= <EXP>
y las producciones P:
<EXP> ::= <EXP> + <TERMINO> |
<EXP> - <TERMINO> |
<TERMINO>
<TERMINO> ::= <TERMINO> * <FACTOR> |
<TERMINO> / <FACTOR> |
<FACTOR>
<FACTOR> ::= <PRIMARIO> ^ <FACTOR> | <PRIMARIO>
<PRIMARIO> ::= - <PRIMARIO> | <ELEMENTO>
<ELEMENTO> ::= ( <EXP> ) | identificador | constante
Entonces el rbol de anlisis sintctico de la sentencia a * c + d ^ 4 se muestra en la
figura 10. Sin embargo esta gramtica es recursiva a izquierdas, este inconveniente se
resolver en el ejemplo del apartado 5.4.5.2.2.
Anlisis Sintctico en Procesadores de Lenguaje
14
<EXP>
<EXP>
+
<TERMINO>
<TERMINO> <FACTOR>
* ^ <TERMINO>
<FACTOR>
<PRIMARIO>
<ELEMENTO>
identificador
a
<FACTOR>
<PRIMARIO>
<ELEMENTO>
identificador
c
<PRIMARIO>
<ELEMENTO>
identificador
d
<FACTOR>
<PRIMARIO>
<ELEMENTO>
constante
4

Figura 10: rbol de anlisis sintctico de a*c+d^4
4.2.7 Gramticas limpias y gramticas sucias
Las gramticas de los lenguajes de programacin estn formadas por un conjunto de
reglas, cuyo nmero suele ser bastante amplio, lo cual incide en la ocultacin de distintos
problemas que pueden producirse, tales como tener reglas que produzcan smbolos que no
se usen despus, o que nunca se llegue a cadenas terminales. Todo esto se puede solventar
realizando la transformacin de la gramtica inicial "sucia" a una gramtica "limpia". A
continuacin se formalizarn estos conceptos por medio de definiciones.
Smbolo muerto: es un smbolo no terminal que no genera ninguna cadena de
smbolos terminales.
Smbolo inaccesible: es un smbolo no terminal al que no se puede llegar por
medio de producciones desde el smbolo inicial.
Smbolo extrao: se denomina as a todo smbolo muerto o inaccesible.
Gramtica sucia: es toda gramtica que contiene smbolos extraos.
Gramtica limpia: es toda gramtica que no contiene smbolos extraos.
Smbolo vivo: es un smbolo no terminal del cual se puede derivar una cadena
de smbolos terminales. Todos los smbolos terminales son smbolos vivos. Es
decir son smbolos vivos lo que no son muertos.
Smbolo accesible: es un smbolo que aparece en una cadena derivada del
smbolo inicial. Es decir, aquel smbolo que no es inaccesible.
4.2.8 Limpieza de gramticas
En el apartado anterior se han definido algunos problemas que pueden presentarse en
una gramtica. Por lo tanto es norma general que toda gramtica en bruto ha de limpiarse
con el objetivo de eliminar todos los smbolos extraos.
Existen algoritmos para depurar y limpiar las gramticas sucias. El mtodo consiste en
detectar en primer lugar todos los smbolos muertos, y a continuacin se detectan todos
los smbolos inaccesibles. Es importante seguir este orden, puesto que la eliminacin de
smbolos muertos puede generar nuevos smbolos inaccesibles.
Anlisis Sintctico en Procesadores de Lenguaje
15
Los algoritmos que se utilizan en la limpieza de gramticas se basan en los teoremas
que se enunciarn a continuacin sobre los smbolos vivos y los smbolos accesibles.
4.2.8.1 Teorema de los smbolos vivos
Si todos los smbolos de la parte derecha de una produccin son vivos, entonces el
smbolo de la parte izquierda tambin lo es.
La demostracin es obvia. Este teorema se utiliza para construir algoritmos de
deteccin de smbolos muertos. El procedimiento consiste en iniciar una lista de no
terminales que sepamos a ciencia cierta que son smbolos vivos, y aplicando el teorema
anterior para detectar otros smbolos no terminales vivos para aadirlos a la lista. Dicho
de otra forma, los pasos del algoritmo son:
1. Hacer una lista de no-terminales que tengan al menos una produccin sin smbolos
no terminales en la parte derecha.
2. Dada una produccin, si todos los no-terminales de la parte derecha pertenecen a la
lista, entonces podemos incluir al no terminal de la parte izquierda.
3. Cuando no se puedan incluir ms smbolos mediante la aplicacin del paso 2, la
lista contendr todos los smbolos vivos, el resto sern muertos.
4.2.8.2 Ejemplo
Sea la gramtica expresada en BNF:
<INICIAL> ::= a <NOTER1> <NOTER2> <NOTER3> | <NOTER4> d
<NOTER1> ::= b <NOTER2> <NOTER3>
<NOTER2> ::= e | de
<NOTER3> ::= g <NOTER2> | h
<NOTER4> ::= <NOTER1> f <NOTER5>
<NOTER5> ::= t <NOTER4> | v <NOTER5>
Determinar los smbolos muertos.
Se aplican los pasos:
1. Confeccin de la lista: slo hay un smbolo no terminal con el cual comenzar la
lista.
<NOTER2> <NOTER3>
2. Aplicando el teorema se incluyen en la lista:
<NOTER1>
<INICIAL>
3. No se puede aplicar el teorema ms veces, por lo tanto la lista de smbolos vivos
est completa y los smbolos <NOTER4> y <NOTER5> son no terminales muertos.
Anlisis Sintctico en Procesadores de Lenguaje
16
4.2.8.3 Teorema de los smbolos accesibles
Si el smbolo no terminal de la parte izquierda de una produccin es accesible,
entonces todos los smbolos de la parte derecha tambin lo son.
La demostracin es obvia. El teorema se puede utilizar para construir algoritmos,
consistiendo stos en el siguiente procedimiento: se hace una lista inicial de smbolos no
terminales que se sabe a ciencia cierta que son accesibles, y aplicando el teorema para
detectar nuevos smbolos accesibles para aadir a la lista, hasta que no se pueden
encontrar ms.
Los pasos a seguir son:
1. Se comienza la lista con un nico no terminal, el smbolo inicial.
2. Si la parte izquierda de la produccin est en la lista, entonces se incluyen en la
misma a todos los no terminales que aparezcan en la parte derecha.
3. Cuando ya no se puedan incluir ms smbolos mediante la aplicacin del paso 2, la
lista contendr todos los smbolos accesibles, y el resto ser inaccesible.
4.2.8.4 Ejemplo
Sea la siguiente gramtica en notacin BNF:
<INICIAL> ::= a <NOTER1> <NOTER2> | <NOTER1>
<NOTER1> ::= c <NOTER2> d
<NOTER2> ::= e | f <INICIAL>
<NOTER3> ::= g <NOTER4> | h <NOTER4> t
<NOTER4> ::= x | y | z
Determinar los smbolos inaccesibles
Aplicando los pasos:
1. Confeccin de la lista: <INICIAL>
2. Se incluyen en la lista: <NOTER1>
<NOTER2>
3. No se puede aplicar ms veces el paso, luego la lista de smbolos accesibles est
completa, y los no terminales inaccesibles son:
<NOTER3>
<NOTER4>
4.2.8.5 Anlisis automtico de la limpieza de gramticas
Los algoritmos de limpieza de gramticas son fciles de programar, y comprueban si
las gramticas son limpias. El primer paso para el tratamiento de cualquier gramtica es la
eliminacin de los smbolos extraos. En la Universidad de Oviedo se ha desarrollado un
Anlisis Sintctico en Procesadores de Lenguaje
17
analizador de gramticas LL(1)
1
que toma como entrada una gramtica descrita en el
formato BNF sin la opcin alternativa e indica si la gramtica es LL(1) o no sealando las
reglas de la gramtica que se lo impiden. El primer paso de este analizador es verificar si
la gramtica es limpia. La gramtica de los ejemplos anteriores debe introducirse en el
formato siguiente:
<INICIAL>::= A <NOTER1> <NOTER2>
<INICIAL>::= <NOTER1>
<NOTER1>::= C <NOTER2> D
<NOTER2>::= E
<NOTER2>::= F <INICIAL>
<NOTER3>::= G <NOTER4>
<NOTER3>::= H <NOTER4> T
<NOTER4>::= X
<NOTER4>::= Y
<NOTER4>::= Z
El analizador indicara que la gramtica no es limpia. En el men correspondiente se
puede obtener la relacin de smbolos no accesibles. En este caso, NOTER3 y NOTER4.
4.3 FORMAS NORMALES DE CHOMSKY Y GREIBACH
Sucede con frecuencia en lingstica matemtica, que en algunas ocasiones es
imprescindible que las gramticas se hallen dispuestas de una forma especial. Es decir, se
trata de obtener una gramtica equivalente, que genera el mismo lenguaje, pero que debe
cumplir unas especificaciones determinadas. A continuacin se muestran las dos formas
normalizadas ms frecuentes, que se emplean en los lenguajes formales y sus
aplicaciones.
4.3.1 Forma Normal de Chomsky (FNC)
Una gramtica se dice que est en la Forma Normal de Chomsky si sus reglas son de
una de estas formas:
A BC
A a
Siendo A, B, C no terminales y a un terminal.
4.3.1.1 Teorema de la forma normal de Chomsky
Toda gramtica libre de contexto sin la cadena vaca tiene una gramtica equivalente
cuyas producciones estn en la Forma Normal de Chomsky.

1
Las condiciones de las gramticas LL(1) se definen en el tema 5
Anlisis Sintctico en Procesadores de Lenguaje
18
4.3.1.2 Ejemplo
Sea la gramtica G= ( VN={S, A, B}, VT={a, b}, P, S) cuyas producciones son:
S bA|aB
A bAA | aS | a
B aBB | bS | b
encontrar una gramtica equivalente en FNC.
En primer lugar las reglas pueden rescribirse:
(1) S bA
(2) S aB
(3) A bAA
(4) A aS
(5) (*) A a
(6) B aBB
(7) B bS
(8) (*) B b
Solamente las sealadas con (*) estn en forma FNC. La produccin (1) puede
sustituirse por dos:
S C
b
A
C
b
b
Igualmente la (2) puede sustituirse por
S C
a
B
C
a
a
Las producciones (3) y (4) se sustituyen por
A C
b
D
1
D
1
AA
A C
a
S
y la (6) y la (7) por
B C
a
D
2

D
2
BB
B C
b
S
Entonces la gramtica equivalente en FNC es:
S C
b
A
S C
a
B
Anlisis Sintctico en Procesadores de Lenguaje
19
A C
a
S
A C
b
D
1

A a
B C
b
S
B C
a
D
2

B b
D
1
AA
D
2
BB
C
a
a
C
b
b
4.3.2 Forma Normal de Greibach (FNG)
Se dice que una gramtica est en la Forma Normal de Greibach si sus reglas de
produccin son de la forma:
A a
A a
donde A es un no terminal, a es un terminal y es una cadena compuesta por
terminales y no terminales.
4.3.2.1 Teorema de la forma normal de Greibach
Todo lenguaje de contexto libre sin la cadena vaca puede ser generado por una
gramtica cuyas reglas de produccin estn en la FNG.
4.3.2.2 Ejemplo
La gramtica dada en el ejemplo 4.3.1.2.
S bA|aB
A bAA | aS | a
B aBB | bS | b
Anlisis Sintctico en Procesadores de Lenguaje
20
4.4 TIPOS GENERALES DE ANALIZADORES SINTCTICOS
Atendiendo a la forma en que procesan la cadena de entrada se clasifican en:
Mtodos direccionales: procesan la cadena de entrada smbolo a smbolo de
izquierda a derecha.
Mtodos no-direccionales: acceden a cualquier lugar de la cadena de entrada
para construir el rbol. Necesitan tener toda la cadena de componentes lxicos
en memoria para que el anlisis pueda comenzar.
Atendiendo al nmero de alternativas posibles en una derivacin se clasifican en:
Mtodos deterministas: dado un smbolo de la cadena de entrada se puede
decidir en cada paso cual es la alternativa/derivacin adecuada a aplicar, slo
hay una posible. No se produce retroceso y el coste es lineal.
Mtodos no-deterministas: en cada paso de la construccin del rbol se deben
probar diferentes alternativas/derivaciones para ver cual es la adecuada, con el
correspondiente aumento del coste.
Existen tres tipos generales de analizadores sintcticos:
Analizadores Universales
Los algoritmos ms referenciados para reconocer las gramticas libres de contexto son
el algoritmo CYK introducido en 1963 por Cocke, Younger y Kasami [You67], y el
algoritmo de Early [Ear70]. El primero opera solamente con gramticas libres de contexto
en forma normal de Chomsky mientras que el segundo trata las gramticas libres de
contexto en general. Ambos algoritmos no se consideran demasiado eficientes para ser
utilizados de forma generalizada ya que su coste es exponencial (dependen de la longitud
de la cadena de entrada).
Analizadores Descendentes (top-down)
Construyen el rbol sintctico de la sentencia a reconocer desde el smbolo inicial
(raz), hasta llegar a los smbolos terminales (hojas) que forman la sentencia, usando
derivaciones ms a la izquierda.
Los principales problemas que se plantean son dos: el retroceso (backtraking) y la
recursividad a izquierdas. Para solventar este inconveniente, se opera con gramticas
LL(k), que pueden realizar un anlisis sin retroceso en forma descendente.
Analizadores Ascendentes (bottom-up)
Construyen el rbol sintctico de la sentencia a reconocer desde las hojas hasta llegar a
la raz. Son analizadores del tipo reduccin-desplazamiento (shift-reduce), parten de los
distintos tokens de la sentencia a analizar y por medio de reducciones llegan al smbolo
inicial de la gramtica.
El principal problema que se plantea en el anlisis ascendente es el retroceso. Para
solventar este inconveniente, se definieron distintos tipos de gramticas entre las cuales
Anlisis Sintctico en Procesadores de Lenguaje
21
las ms utilizadas son las LR(k) ya que realizan eficientemente el anlisis ascendente sin
retroceso.
Cabe destacar por tanto, la importancia de los mtodos direccionales y deterministas
en el diseo de los procesadores de lenguaje. El anlisis determinista por la necesidad de
eficiencia (coste lineal frente al exponencial que le hara prohibitivo en la implementacin
prctica de los procesadores de lenguaje) y porque las herramientas para su construccin
asumen esta condicin. Los mtodos direccionales por la propia naturaleza secuencial en
que se van procesando los smbolos del fichero fuente de izquierda a derecha.
4.5 SINTAXIS CONCRETA Y SINTAXIS ABSTRACTA
Se ha visto anteriormente que la sintaxis de un lenguaje de programacin determina el
conjunto de programas legales y la relacin entre los smbolos y frases (o sentencias)
que en ellos aparecen.
La sintaxis concreta, describe todas aquellas caractersticas del lenguaje que estn
diseadas para hacerlo ms claro y legible. Entre estas caractersticas se encuentran las
palabras clave, los delimitadores, los separadores, etc. Estos elementos son irrelevantes
desde el punto de vista estructural e incluso se puede decir que una vez cumplida su
funcin, pueden llegar a estorbar en el proceso de anlisis semntico ya que hacen perder
la visin global de la estructura del lenguaje.
Ejemplo: La siguiente produccin describe la forma que debe tener una sentencia
condicional en algn lenguaje de programacin.
Condicion: SI expresin_logica ENTONCES
COMIENZO sentencia FIN
SINO sentencia FIN
Sin embargo, desde un punto de vista semntico lo nico importante que se necesita
saber es que una sentencia condicional est compuesta de dos grupos de sentencias y de
una expresin lgica que discrimina cual de las dos debe ejecutarse.
Algunos de estos elementos cumplen adems una segunda funcin en la fase de
anlisis sintctico, haciendo que no tengamos que construir reconocedores excesivamente
potentes, o proporcionando puntos de seguridad que ayuden a la recuperacin de errores
sintcticos (el ms tpico es el carcter ;).
La sintaxis abstracta, permite definir los aspectos estructurales de un lenguaje. De
esta forma, al reflejar solo los aspectos esenciales, es posible representar ms claramente
su estructura, y por lo tanto se simplifica el posterior tratamiento semntico ya que un
rbol de anlisis sintctico contiene ms informacin de la que es absolutamente
necesaria para que un procesador de lenguaje genere cdigo ejecutable.
Los rboles sintcticos abstractos representan abstracciones de las secuencias de
tokens del cdigo fuente real.
A continuacin se muestran algunos ejemplos.
Anlisis Sintctico en Procesadores de Lenguaje
22

Ejemplo 1: Sentencia de asignacin
V := E
V E
Set V = E
Todas son diferentes en trminos de sintaxis concreta pero
son las mismas en trminos de estructura.

Ejemplo 2: Sentencia condicinal
while (x!=y)
{
....
}
while (x<>y) do
begin
....
end
Como se puede observar, los dos bloques poseen diferente sintaxis concreta e igual
sintaxis abstracta:
while condicion
bloque
Anlisis Sintctico en Procesadores de Lenguaje
23

TEMA 5 ANLISIS SINTCTICO DESCENDENTE


5.1 INTRODUCCIN
Uno de los mtodos de reconocimiento para las gramticas de contexto libre son los
analizadores sintcticos descendentes o tambin llamados predictivos y orientados
hacia un fin, debido a la forma en que trabajan y construyen el rbol sintctico. Los
analizadores sintcticos descendentes son lo que construyen el rbol sintctico de la
sentencia a reconocer de una forma descendente, comenzando por el smbolo inicial o
raz, hasta llegar a los smbolos terminales que forman la sentencia. El anlisis
descendente es un procedimiento que crea objetivos y subobjetivos al intentar relacionar
una sentencia con su entorno sintctico. Al comprobar los subobjetivos, se verifican y
descartan las salidas falsas y las ramas impropias hasta que se alcanza el subobjetivo
propio que son los smbolos terminales que forman la sentencia.
El procedimiento de anlisis, en cada encuentro con la estructura sintctica, tiene que
examinar los subobjetivos. Si se cumplen los subobjetivos requeridos, entonces, por
definicin, se logra el objetivo de mayor rango. En caso contrario, se descarta dicho
objetivo superior. Esta secuencia de comprobar, descartar y por ltimo efectuar los
objetivos, se programa hacia abajo siguiendo el rbol sintctico hasta que todas las
componentes bsicas se hayan acumulado en componentes de nivel superior o hasta que
se compruebe que la sentencia es errnea.
Los algoritmos que realizan el anlisis descendente deben de cumplir al menos dos
condiciones: a) el algoritmo debe saber en todo momento dnde se encuentra dentro del
rbol sintctico; y b) debe poder elegir los subobjetivos (es decir la regla de produccin
que aplicar).
Histricamente, los compiladores dirigidos por sintaxis, en la forma de anlisis
recursivo descendente fue propuesta por primera vez en forma explcita por Lucas (1961),
para describir un compilador simplificado de ALGOL 60 mediante un conjunto de
subrutinas recursivas, que correspondan a la sintaxis BNF. El problema fundamental para
el desarrollo de este mtodo fue el retroceso, lo que hizo que su uso prctico fuera
restringido. La elegancia y comodidad de la escritura de compiladores dirigidos por
sintaxis fue pagada en tiempo de compilacin por el usuario.
La situacin cambi cuando comenz a realizarse anlisis sintctico descendente sin
retroceso, por medio del uso de gramticas LL(1), obtenidas independientemente por
Foster (1965) y Knuth (1967). Generalizadas posteriormente por Lewis, Rosenkrantz y
Stearns en 1969, dando lugar a las gramticas LL(k), que pueden analizar sintcticamente
sin retroceso, en forma descendente, examinando en cada paso todos los smbolos
procesados anteriormente y los k smbolos de entrada ms a la derecha.
Anlisis Sintctico en Procesadores de Lenguaje
24
5.2 El problema del retroceso
El primer problema que se presenta con el anlisis sintctico descendente, es que a
partir del nodo raz, el analizador sintctico no elija las producciones adecuadas para
alcanzar la sentencia a reconocer. Cuando el analizador se da cuenta de que se ha
equivocado de produccin, se tienen que deshacer las producciones aplicadas hasta
encontrar otras producciones alternativas, volviendo a tener que reconstruir parte del
rbol sintctico. A este fenmeno se le denomina retroceso, vuelta atrs o en ingls
backtracking.
El proceso de retroceso puede afectar a otros mdulos del compilador tales como
tabla de smbolos, cdigo generado, etc. teniendo que deshacerse tambin los procesos
desarrollados en estos mdulos.
5.2.1 Ejemplo de retroceso
Para explicar mejor el problema del retroceso, se estudiar el siguiente ejemplo, donde
se utiliza la gramtica G = (VN, VT, S, P) donde:
VN={<PROGRAMA>, <DECLARACIONES>, <PROCEDIMIENTOS>}
VT={module, d, p, ;, end}
S=<PROGRAMA>
las reglas de produccin P son las siguientes:
<PROGRAMA> ::= module <DECLARACIONES>; <PROCEDIMIENTOS> end
<DECLARACIONES> ::= d | d; <DECLARACIONES>
<PROCEDIMIENTOS> ::= p | p; <PROCEDIMIENTOS>
Se desea analizar la cadena de entrada siguiente: module d ; d ; p ; p end
A continuacin se construye el rbol sintctico de forma descendente:
1. Se parte del smbolo inicial
<PROGRAMA>
2. Aplicando la primera regla de produccin de la gramtica se obtiene:
<PROGRAMA>
module <DECLARACIONES> ; <PROCEDIMIENTOS> end

3. Aplicando las derivaciones ms a la izquierda, se tiene que:
3.1 module es un smbolo terminal, que coincide con el primero de la cadena a
reconocer.
3.2 se deriva <DECLARACIONES> con la primera alternativa.
Anlisis Sintctico en Procesadores de Lenguaje
25
<PROGRAMA>
module <DECLARACIONES> ; <PROCEDIMIENTOS> end
d

Se comprueba que el nuevo terminal generado d coincide con el segundo token de la
cadena de entrada.
3.3 ; coincide con el tercer token de la cadena de entrada.
3.4 Se deriva <PROCEDIMIENTOS> con la primera alternativa.
<PROGRAMA>
module <DECLARACIONES> ; <PROCEDIMIENTOS> end
d p

y se llega a un terminal que no es el que tiene la cadena.
Se deriva con la segunda alternativa.
<PROGRAMA>
odule <DECLARACIONES> ; <PROCEDIMIENTOS> end
d ; p <PROCEDIMIENTOS>

Se observa que el siguiente terminal generado, tampoco coincide con el token de la
cadena de entrada. Entonces el analizador sintctico debe de volver atrs, hasta encontrar
la ltima derivacin de un no terminal, y comprobar si tiene alguna alternativa ms. En
caso afirmativo se debe de elegir la siguiente y probar. En caso negativo, volver ms
atrs para probar con el no terminal anterior. Este fenmeno de vuelta atrs es el que se
ha definido anteriormente como retroceso.
Si en este proceso de marcha atrs se llega al smbolo inicial o raz, se tratara de una
cadena errnea sintcticamente.
Dado que no se ha encontrado el terminal de la cadena de entrada, se ha de volver atrs
hasta el no-terminal analizado anteriormente, que en este caso es <DECLARACIONES>,
es decir, se pasa a 3.2 y en vez de tomar la primera alternativa se toma la segunda
alternativa.
Anlisis Sintctico en Procesadores de Lenguaje
26
<PROGRAMA>
module <DECLARACIONES> ; <PROCEDIMIENTOS> end
; d <DECLARACIONES>

Llegados a este punto, tambin se debe de retroceder en la cadena de entrada hasta la
primera d, ya que en el proceso de vuelta atrs lo nico vlido que nos ha quedado del
rbol ha sido el primer token module. Si se deriva <DECLARACIONES> nuevamente
con la primera alternativa, se tiene
<PROGRAMA>
module <DECLARACIONES> ; <PROCEDIMIENTOS> end
; d <DECLARACIONES>
d

En este momento se tienen reconocidos los primeros 5 tokens de la cadena.
Se deriva el no terminal <PROCEDIMIENTOS>, con la primera alternativa, y el rbol
resultante se muestra a continuacin. Se ha reconocido el sexto token de la cadena.
<PROGRAMA>
module <DECLARACIONES> ; <PROCEDIMIENTOS> end
; d <DECLARACIONES>
d
p

El siguiente token de la cadena es ; mientras que en el rbol se tiene end, por lo tanto
habr que volver atrs hasta el anterior no terminal, y mirar si tiene alguna otra
alternativa.
El ltimo no terminal derivado es <PROCEDIMIENTOS>, si se deriva con su otra
alternativa se tiene el rbol mostrado a continuacin. Con esta derivacin se ha
reconocido la parte de la cadena de entrada module d ; d ; p ;
<PROGRAMA>
module <DECLARACIONES> ; <PROCEDIMIENTOS> end
; d <DECLARACIONES>
d
p ; <PROCEDIMIENTOS>

Se deriva <PROCEDIMIENTOS> con la primera alternativa y se obtiene el siguiente
rbol sintctico, reconocindose module d ; d ; p ; p
Anlisis Sintctico en Procesadores de Lenguaje
27
<PROGRAMA>
module <DECLARACIONES> ; <PROCEDIMIENTOS> end
; d <DECLARACIONES>
d
p ; <PROCEDIMIENTOS>
p

El rbol sintctico ya acepta el siguiente token de la cadena end y por tanto la cadena
completa.
Puede concluirse, que los tiempos de reconocimiento de sentencias de un lenguaje
pueden dispararse a causa del retroceso, por lo tanto los analizadores sintcticos deben
eliminar las causas que producen el retroceso.
5.3 Anlisis sintctico descendente con retroceso
La forma en que se hace el anlisis descendente con retroceso ya se ha visto en el
ejemplo del apartado 5.2.1, aqu se expone el algoritmo general para construir un
analizador sintctico descendente con retroceso.
5.3.1 Algoritmo de anlisis sintctico descendente con retroceso
1. Se colocan las reglas de la gramtica segn un orden preestablecido para cada uno de
los no terminales de la que est compuesta.
2. Se comienza la construccin del rbol sintctico a partir del smbolo inicial, y
aplicando las siguientes reglas en forma recursiva. Al nodo en proceso de expansin
en un determinado momento se le llamar nodo activo.
3. Cada nodo activo escoge la primera de sus alternativas posibles, por ejemplo, para el
no terminal A con la regla A x
1
x
2
...x
n
crea n descendientes directos, y el nodo
activo en ese momento pasa a ser el primer descendiente por la izquierda. Cuando se
deriven todos los descendientes, pasar a ser nodo activo el ms inmediato derecho de
A susceptible de ser derivado. En el caso de que la regla fuese: A x
1
| x
2
|...| x
n
se
elegir en un principio la alternativa de ms a la izquierda.
4. Si el nodo activo es un terminal deber entonces compararse el smbolo actual de la
cadena a analizar con dicho nodo. Si son iguales se avanza un token de entrada y el
nuevo smbolo actual ser el situado ms a la derecha del terminal analizado. Si no
son iguales se retrocede hasta un nodo no terminal y se reintenta eligiendo la
siguiente alternativa. Si an as no existe ninguna alternativa posible se retrocede al
no terminal anterior, y as sucesivamente. Si se llega al smbolo inicial la cadena no
pertenece al lenguaje.
5.3.2 Corolario
Una gramtica de contexto libre, que es una gramtica limpia y no es recursiva a
izquierdas, para cualquier cadena de smbolos de su alfabeto terminal existe un nmero
finito de posibles anlisis a izquierda desde el smbolo inicial para reconocerla o no.
Anlisis Sintctico en Procesadores de Lenguaje
28
A partir de todo lo expresado anteriormente, se pueden construir analizadores
sintcticos descendentes con retroceso. Su principal problema es el tiempo de ejecucin.
5.4 Anlisis descendente sin retroceso
Para eliminar el retroceso en el anlisis descendente, se ha de elegir correctamente la
produccin correspondiente a cada no terminal que se expande. Es decir que el anlisis
descendente ha de ser determinista, y slo se debe de dejar tomar una opcin en la
expansin de cada no terminal.
5.4.1 Gramticas LL(k)
Las gramticas LL(k) son un subconjunto de las gramticas libres de contexto.
Permiten un anlisis descendente determinista (o sin retroceso), por medio del
reconocimiento de la cadena de entrada de izquierda a derecha ("Left to right") y que va
tomando las derivaciones ms hacia la izquierda ("Leftmost") con slo mirar los k tokens
situados a continuacin de donde se halla. Si k=1 se habla de gramticas LL(1).
Las gramticas LL(1) permiten construir un analizador determinista descendente con
tan slo examinar en cada momento el smbolo actual de la cadena de entrada (smbolo de
preanlisis) para saber que produccinn aplicar.
Antes de definir completamente las gramticas LL(1), se definirn otros tipos de
gramticas ms sencillas que son LL(1). La introduccin de estas gramticas permitir
una aproximacin paso a paso hacia la definicin completa de las gramticas LL(1).
5.4.1.1 Teorema
Una gramtica LL(k) es no ambigua.
Una gramtica LL(k) no es recursiva a izquierdas.
Demostracin: Por definicin de gramtica LL(k).
5.4.1.2 S-gramticas
Las S-gramticas son un subconjunto muy restringido de las gramticas LL(1), debido
a dos condiciones muy fuertes.
Una S-gramtica es una gramtica libre de contexto que debe cumplir las siguientes
dos condiciones:
1) Todas las partes derechas de cada produccin comienzan con un smbolo
terminal.
2) Si dos producciones tienen la misma parte izquierda, entonces su parte derecha
comienza con diferentes smbolos terminales. Es decir las distintas
producciones de cada no terminal, deben comenzar por distinto smbolo
terminal. As si se tienen la producciones del no terminal A: A a
1

1
| a
2

2
|
...| a
m

m
se debe cumplir que:
Anlisis Sintctico en Procesadores de Lenguaje
29
a
i
a
j
i j

a
i
VT
i
V
*
1 i m
La primera condicin es similar a decir que la gramtica est en la Forma Normal de
Greibach (FNG).
La segunda condicin ayudar a escribir los analizadores sintcticos descendentes sin
retroceso (o deterministas), ya quepermitirn siempre elegir la derivacin correcta, con
slo mirar un token hacia adelante.
5.4.1.3 Corolario de la definicin de S-gramticas
Toda S-gramtica es LL(1), la inversa no es cierta. Toda S-gramtica es una gramtica
LL(1) dado que una vez que analiza un token es posible determinar que regla de
produccin se debe aplicar.
Contraejemplo de S-gramtica
Sea la gramtica:
(1) S a T
(2) S T b S
(3) T b T
(4) T b a
No es una s-gramtica, ya que la parte derecha de la produccin (2) no comienza por
un terminal como exige la primera condicin. Adems las producciones (3) y (4) no
cumplen la segunda condicin..
Ejemplo de S-gramtica
Sea la gramtica:
S a b R
S b R b S
R a
R b R
Obviamente es una s-gramtica, y por tanto es una gramtica LL(1).
Ejemplo de S-gramtica
Sea la gramtica:
S p X
S q Y
X a X b
X x
Y a Y d
Y y
Obviamente es una s-gramtica, y por tanto es una gramtica LL(1).
Anlisis Sintctico en Procesadores de Lenguaje
30
5.4.1.4 Conjunto de smbolos INICIALES o cabecera
Se define el conjunto de smbolos iniciales o cabecera (en ingls FIRST) de un
smbolo (VT VN)
*
, como el conjunto de smbolos terminales que pueden aparecer
al principio de cadenas derivadas de . La definicin anterior se puede expresar como:
INICIALES() = {a / a ... siendo a VT }
Si a
1
...
n
entonces {a} INICIALES() con a VT
Si entonces {} INICIALES()
Algoritmo para calcular el conjunto de INICIALES
Clculo del conjunto de INICIALES para todos los smbolos gramaticales.
Repetir hasta que no se puedan aadir ms smbolos terminales o al conjunto.
1. Si X es un terminal o , entonces INICIALES (X) = {X}
2. Si X es un no terminal, entonces para cada produccin de la forma XX
1
X
2
...X
n
,
INICIALES (X) contiene a INICIALES (X
1
) {}. Si tambin para algn i < n
todos los conjuntos INICIALES (X
1
) ... INICIALES (X
i
) contienen a , entonces
INICIALES (X) contiene a INICIALES (X
i+1
) {}. Si todos los conjuntos
INICIALES (X
1
) ... INICIALES (Xn) contiene , entonces INICIALES (X)
tambin contiene a .
3. Se define INICIALES (), para cualquier cadena = X
1
X
2
...X
n
, de terminales y no
terminales, de la manera siguiente. INICIALES () contiene a INICIALES (X
1
)
{}. Para cada i = 2, ...,n si INICIALES (X
k
) contiene a para toda k = 1, ...i-1,
entonces INICIALES() contiene a INICIALES (X
i
) - {}. Finalmente, si para
todo i = 1...n, INICIALES (X
i
) contiene , entonces INICIALES () contiene a .
El seudocdigo del algoritmo para todo no terminal A es el siguiente:

for todo no terminal A do INICIALES(A) := {}
while existan cambios en cualquier INICIALES(A) do
for cada seleccin de produccin AX
1
X
2
...X
n
do
k :=1; continuar = verdadero;
while contiuar = verdadero and k <= n do
aadir INICIALES(X
k
)- {} a INICIALES(A)
if no esta en INICIALES(X
k
) then continuar := falso;
k := k+1;
if continuar = verdadero then agregar a INICIALES(A);

Ejemplo de clculo del conjunto de Iniciales
Sea la gramtica
*
*
*
Anlisis Sintctico en Procesadores de Lenguaje
31
S A B e
A d B
A a S
A c
B A S
B b
INICIALES(A)={d, a, c}
INICIALES(S)=INICIALES(A)={d, a, c}
INICIALES(B)=INICIALES(A) {b} ={d, a, c, b}
5.4.1.5 Gramticas LL(1) simples
Las gramticas LL(1) simples son un subconjunto de las gramticas LL(1), con las dos
restricciones siguientes:
No se permiten smbolos no terminales que deriven a vaco. Es decir no se
permiten producciones vacas o reglas-, cuya parte derecha es la cadena
vaca .
Las distintas producciones de cada no terminal A VN A
1
|
2
| ...|
n

deben cumplir los conjuntos INICIALES(
1
), INICIALES(
2
),...,
INICIALES(
n
) son disjuntos entre s, es decir INICIALES(
i
)
INICIALES(
j
) = i j
5.4.1.6 Corolario de las gramticas LL(1) simples
Toda gramtica LL(1) simple es LL(1), la inversa no es cierta.
5.4.1.7 Teorema de equivalencia entre gramticas LL(1) y S-gramticas
Dada una gramtica LL(1) simple siempre es posible encontrar una S-gramtica
equivalente.
Ejemplo de gramtica LL(1) simple
Sea la gramtica cuyas reglas de produccin son:
S A B e
A d B
A a S
A c
B A S
B b
Se desea verificar si es o no LL(1) simple.
4. Dadas las producciones A d B | a S | c
Anlisis Sintctico en Procesadores de Lenguaje
32
INICIALES (dB) INICIALES(aS) INICIALES(c) = {d} {a} {c}= 0
5. Dadas las producciones B A S | b
INICIALES (AS) INICIALES(b) = {a,c,d} {b} = 0
Luego esta gramtica es LL(1) simple.
Ejemplo de gramtica LL(1) simple
Sea la siguiente gramtica que incluye una produccin para reconocer el carcter final
de la cadena de entrada.
(0) S S #
(1) S a S
(2) S b A
(3) A d
(4) A c c A
Claramente es una gramtica LL(1) simple, que permite reconocer una cadena sin
retroceso. As con esta gramtica se desea reconocer la cadena: aabccd#. Realizando
derivaciones ms a la izquierda se obtiene:
<S> <S># aa <S># aab <A># aabcc <A># aabccd#
siendo el rbol sintctico el que se muestra en la figura 11.
<S'>
<S> #
<S>
<S>
<A>
<A>
a
a
b
c c
d

Figura 11: Reconocimiento sin retroceso de aabccd#
5.4.1.8 Conjunto de smbolos SEGUIDORES o siguientes
Dada una gramtica libre de contexto con un smbolo inicial S, para un smbolo no
terminal A, se dice que el conjunto de smbolos SEGUIDORES(A) es el conjunto de
smbolos terminales que en cualquier momento de la derivacin pueden aparecer
inmediatamente a la derecha de (o despus de) A. La definicin formal se puede expresar
de la siguiente forma:
SEGUIDORES (A) = { a / S Aa con , (VT VN)
*
}
*
Anlisis Sintctico en Procesadores de Lenguaje
33
Los smbolos seguidores o siguientes (en ingls FOLLOW) de un smbolo no terminal
tambin se pueden definir como los smbolos iniciales del smbolo que sigue al no
terminal A.
Algoritmo para calcular el conjunto de SEGUIDORES
Clculo del conjunto de SEGUIDORES para los smbolos A V
NT
.
Repetir hasta que no cambie el conjunto de seguidores
1. Si A es el smbolo inicial, entonces $ est en SEGUIDORES (A).
2. Si hay una produccin B A, entonces
INICIALES () {} SEGUIDORES (A)
3. Si existe una produccin B A B A tal que INICIALES ()
entonces SEGUIDORES (B) SEGUIDORES (A).
El seudocdigo del algoritmo para el clculo de conjuntos de Seguidores.

SEGUIDORES (smbolo-inicial) := {$};
for todos los no terminales A smbolo-inicial do SEGUIDORES(A):={};
while existan cambios en cualquier conjunto SEGUIDORES do
for cada produccin AX
1
X
2
...X
n
do
for cada X
i
que sea un no terminal do
aadir INICIALES (X
i+1
X
i+2
...X
n
) - {} a SEGUIDORES (X
i
)
(* NOTA: si i=n, entonces X
i+1
X
i+2
...X
n
= *)
if est en INICIALES(X
i+1
X
i+2
...X
n
) then
aadir SIGUIENTE (A) a SIGUIENTE (X
i
)

Ejemplo de clculo del conjunto de Seguidores
Sea la gramtica:
<PROGRAMA> ::= module <DECLARACIONES> <PROCEDIMIENTOS> end
<DECLARACIONES> ::= d <A>
<A> ::= <vaco> | ; <DECLARACIONES>
<PROCEDIMIENTOS> ::= p <B>
<B> ::= <vaco> | ; <PROCEDIMIENTOS>
Clculo del conjunto de seguidores del smbolo no terminal A.
<PROGRAMA> module d <A> <PROCEDIMIENTOS> end
INICIALES (<PROCEDIMIENTOS>) = { p }
Clculo de los smbolos seguidores de <B>
Anlisis Sintctico en Procesadores de Lenguaje
34
<PROGRAMA> module <DECLARACIONES> p <B> end
SEGUIDORES(<B>)= { end }
5.4.1.9 Conjunto de smbolos DIRECTORES
Los smbolos directores (en ingls SELECT) de una produccin como su nombre
indica son los que dirigen al analizador sintctico para elegir la alternativa adecuada, se
pueden definir como el conjunto de smbolos terminales que determinarn que expansin
de un no terminal se ha de elegir en un momento dado, con solo mirar un smbolo hacia
adelante. La definicin formal se puede enunciar de la siguiente forma, dada una
produccin A donde A es un smbolo no terminal, y es una cadena de smbolos
terminales y no terminales. Entonces se define conjunto de smbolos directores SD(A,
) de una produccin A como:
INICIALES () si es no anulable
INICIALES () SEGUIDORES (A) si es anulable
Ejemplo de clculo del conjunto de smbolos Directores
Sea la gramtica:
<PROGRAMA> ::= module <DECLARACIONES> <PROCEDIMIENTOS> end
<DECLARACIONES> ::= d <A>
<A> ::= <vaco> | ; <DECLARACIONES>
<PROCEDIMIENTOS> ::= p <B>
<B> ::= <vaco> | ; <PROCEDIMIENTOS>
<vaco> ::=
a) Calcular el conjunto de smbolos directores de la produccin A vaco
En este caso es es anulable, luego:
SD (<A>,<vaco>) = INICIALES(<vaco>) SEGUIDORES (<A>) = {p} = { p }
b) Calcular el conjunto de smbolos directores de la produccin:
A ; DECLARACIONES
SD(<A>, ; <DECLARACIONES>) = { ; }
5.4.1.10 Definicin de las gramticas LL(1)
La condicin necesaria y suficiente para que una gramtica limpia sea LL(1), es que
los smbolos directores correspondientes a las diferentes expansiones de cada smbolo no
terminal sean conjuntos disjuntos.
La justificacin de esta condicin es simple. La condicin es necesaria, puesto que si
un smbolo aparece en dos conjuntos de smbolos directores, el analizador sintctico
descendente no puede decidir (sin recurrir a informacin posterior) que expansin
aplicar. La condicin es suficiente, puesto que el analizador siempre puede escoger una
expansin como un smbolo dado, y esta alternativa ser siempre correcta. Si el smbolo
SD (A, )
Anlisis Sintctico en Procesadores de Lenguaje
35
no est contenido en ninguno de los conjuntos de los smbolos directores, la cadena de
entrada no pertenecer al lenguaje y se tratar de un error.
Existe un algoritmo diseado por Lewis et al. [LEWI76, pg. 262-276], traducido y
adaptado por Snchez Dueas et al. [SANC84, pg 86-95], que permite decidir si una
gramtica es o no LL(1). Es decir, si con el diseo de una gramtica para un lenguaje de
programacin, se puede construir un analizador sintctico descendente determinista, con
solo examinar el siguiente token de entrada. Los distintos pasos del algoritmo se
encaminan a construir los conjuntos de smbolos directores para cada expansin de un
smbolo no terminal, y aplicar la condicin necesaria y suficiente para que la gramtica
sea LL(1). Ver anexo I.
5.4.2 Condiciones de las gramticas LL(1)
La definicin original dada por D.E. Knuth, de gramticas LL(1) consta de cuatro
condiciones equivalentes a la definicin dada en la seccin anterior.
5.4.2.1 Primera condicin de Knuth
No se permitirn producciones de la forma A A donde A a VN y V
*
. Esta
condicin equivale a no admitir la recursividad a izquierdas.
5.4.2.2 Segunda condicin de Knuth
Los smbolos terminales que pueden encabezar las distintas alternativas de una regla
de produccin deben formar conjuntos disjuntos. Es decir, si
A B | C A,B,C VN
, V
*

No debe ocurrir que
B dS d VT
C d S, V
*

Esto implica que en todo momento, el smbolo terminal que estamos examinando
seala sin ambigedad, que alternativa se ha de escoger sin posibilidad de error y, en
consecuencia, sin retroceso.
5.4.2.3 Tercera condicin de Knuth
Si una alternativa de una produccin de un smbolo no terminal origina la cadena
vaca, los smbolos terminales que pueden seguir a dicho no-terminal en la produccin
donde aparezca, han de formar un conjunto disjunto con los terminales que pueden
encabezar las distintas alternativas de dicho terminal (INICIALES (A)
SEGUIDORES(A) = ). Expresado de otra forma, sea la cadena A
1
... A
2
... A
3
A
4
A
5
y sea
A
3
el smbolo que se est analizando, adems se tienen las producciones:
A
3
ax |
A
4
A
3
ay
*
*
*
Anlisis Sintctico en Procesadores de Lenguaje
36
Dado que A
3
puede derivar a la cadena vaca, puede darse el caso de que:
INICIALES(A
3
)= { a }
INICIALES(A
4
)= { a }
y no puede determinarse si se ha de elegir la produccin de A
3
o de A
4
.
Esta condicin garantiza que para aquellos smbolos que pueden derivar la cadena
vaca, el primer smbolo que generan y el siguiente que puede aparecer detrs de ellos
sean distintos, sino no se podra saber si se va a empezar a reconocer el no terminal o por
el contrario ya se ha reconocido.
5.4.2.4 Cuarta condicin de Knuth
Ningn smbolo no terminal puede tener dos o ms alternativas que conduzcan a la
cadena vaca. Esta condicin deriva de la anterior. As por ejemplo no se permite:
X A | B
A | C
B | D
5.4.3 Transformacin de gramticas
En el apartado anterior se ha visto la forma de determinar si una gramtica es LL(1) o
no. Sin embargo, la cuestin de si un lenguaje posee una gramtica LL(1) es indecidible.
Es decir, no se puede saber si un determinado lenguaje puede ser generado o no por una
gramtica LL(1), hasta que no se encuentre esta gramtica. No existe ningn algoritmo
general que transforme una gramtica a LL(1). Pero en algunos casos se puede obtener
una gramtica equivalente por medio de las transformaciones que se estudiarn en los
siguientes epgrafes. Ha de tenerse en cuenta que las gramticas LL(1) son un
subconjunto muy particular de las gramticas libres de contexto, tal como se muestra en el
diagrama de la figura 12.

LL(1)
NO AMBIGUAS
GRAMTICAS LIBRES
DE CONTEXTO

Figura 12: Las gramticas LL(1) como subconjunto de las gramticas libres de contexto
5.4.3.1 La recursividad a izquierdas
Se dice que una gramtica tiene recursividad a izquierdas ("left-recursive"), si existe
un no terminal A, tal que para algn V
*
existe una derivacin de la forma:
Anlisis Sintctico en Procesadores de Lenguaje
37

+

donde el signo + encima de la flecha indica que al menos existe una produccin.
Si el analizador sintctico toma el primer no terminal (el ms a la izquierda), el rbol
sintctico es el de la figura 13. La recursividad a izquierdas deja al analizador sintctico
en un bucle infinito, ya que al expandir A, se encontrara con otro A, y as sucesivamente,
sin avanzar en la comparacin del token de entrada.
A
A
A
A
A

Figura 13: Recursividad a izquierda
Ejemplo de recursividad a izquierdas
Sea la gramtica G = (VN, VT, S, P) donde VN = {A, S}, VT = {a, b, c}, y las reglas
de produccin P son las siguientes:
S aAc
A Ab |
El lenguaje reconocido por esta gramtica es L = {a b
n
c / n>=0}. Supongamos que el
analizador sintctico desea reconocer la cadena: abbc, se obtienen los rboles sintcticos
de la figura 14. No saliendo el analizador sintctico de este bucle.
S
(1)
S
a A c
(2)
S
a A c
A b
(3)
S
a A c
A b
A b
(4)
S
a A c
A b
A b
A b
(5)

Figura 14: Bucle infinito tratando de reconocer abbc.
Una forma de resolver este problema es volviendo a escribir la segunda produccin de
la gramtica de esta otra manera:
A | Ab
entonces se construyen los rboles sintcticos que se muestran en la figura 15.
Anlisis Sintctico en Procesadores de Lenguaje
38
S
(1)
S
a A c
(2)
S
a A c
(3)
S
a A c
A b
(4)
S
a A c
A b
A b
(5)

Figura 15: Reconocimiento de la cadena abbc.
Otra forma de resolver el problema es cambiando en la regla conflictiva el orden Ab
por bA, que a su vez cambia la gramtica, pero se obtiene una gramtica equivalente, es
decir que genera el mismo lenguaje.
A | bA
Para reconocer abbc se obtienen los rboles sintcticos de la figura 16.
S
(1)
S
a A c
(2)
S
a A c
b A
(3)
S
a A c
b A
b A
(4)
S
a A c
b A
(5)
b
A

Figura 16: Reconocimiento de la cadena abbc.
No siempre es tan fcil eliminar la recursividad a izquierdas, por tanto, se deben
estudiar otros mtodos para eliminar la recursividad a izquierdas.
5.4.3.2 Eliminacin de la recursividad a izquierdas
Una gramtica con producciones recursivas a izquierda no cumple las condiciones
necesarias para que sea LL(1). Greibach, en 1964, demostr tericamente que es posible
eliminar la recursividad a izquierdas de cualquier gramtica. A continuacin se muestra el
caso ms simple, sea una gramtica con el par de producciones, presentando la primera
una recursividad a izquierdas inmediata:
A A
donde no comienza por A, se puede eliminar la recursividad a izquierdas con slo
reemplazar este par por:
A C
C C
Se puede observar que si se desea reconocer la cadena por medio de producciones
ms a la izquierda se obtienen los rboles sintcticos de la figura 17.
Anlisis Sintctico en Procesadores de Lenguaje
39
A
C
C
C

A
A
A
A
Bucle infinito
Figura 17: rboles sintcticos con y sin recursividad a izquierdas
En general, para eliminar la recursividad a izquierdas de todas las producciones de A,
lo que se hace es agrupar dichas producciones de la siguiente forma:
A A
1
| A
2
| ... | A
n
|
1
|
2
|
3
| ... |
n

donde ningn
i
comienza por A. Seguidamente, se reemplazan las producciones de A
por:
A
1
C |
2
C | ... |
n
C
C
1
C |
2
C | ... |
n
C |
Este proceso elimina todas las recursividades a izquierda inmediatas (siempre que
ningn
i
sea la cadena vaca), pero no se eliminan las recursividades a izquierda
indirectas. Se dice que existe recursividad indirecta cuando no se observan recursividades
directas inmediatas, pero se llegan a recursividades a izquierda inmediatas por medio de
dos o ms derivaciones de las reglas. El mtodo para resolver las recursividades indirectas
es convertirlas a recursividades inmediatas por medio de sustituciones.
Ejemplo 1: Recursividad a izquierdas indirecta
Sea la gramtica, en la que <EXPRESION> presenta una recursividad a izquierda
indirecta:
(1) <EXPRESION> ::= <LISTA>
(2) <LISTA> ::= identificador | <EXPRESION> + identificador
Para eliminarla se realiza la sustitucin de la produccin (2) en (1), quedando la nueva
gramtica equivalente con una recursividad a izquierdas inmediata:
<EXPRESION> ::= identificador | <EXPRESION> + identificador
Ahora se puede aplicar el mtodo estudiado anteriormente para eliminar la
recursividad a izquierda inmediata, obtenindose la siguiente gramtica no recursiva a
izquierdas:
(1) <EXPRESION> ::= identificador <OTRA_EXP>
(2) <OTRA_EXP> ::= + identificador <OTRA_EXP> |
Ejemplo 2: Recursividad a izquierdas indirecta
Sea la siguiente gramtica que presenta una recursividad a izquierdas indirecta:
Anlisis Sintctico en Procesadores de Lenguaje
40
<PROGRAMA> ::= <BLOQUE> .
<BLOQUE> ::= <SENTENCIAS> ;
<SENTENCIAS> ::= <PROCEDURE> return | end
<PROCEDURE> ::= <BLOQUE> exit
Esta gramtica es recursiva a izquierdas indirectamente tal como se muestra en el rbol
sintctico de la figura 18.
<PROGRAMA>
<BLOQUE>
<SENTENCIAS>
<PROCEDURE>
<BLOQUE>
.
;
return
exit
(se vuelve a repetir desde <BLOQUE>)

Figura 18: Recursividad a izquierdas indirecta
La produccin que hace que sea recursiva a izquierdas es la siguiente:
<PROCEDURE> ::= <BLOQUE> exit
Se puede sustituir <BLOQUE> por su parte derecha en la 2 produccin quedando:
<PROCEDURE> ::= <SENTENCIAS> ; exit
Pero an as sigue siendo recursiva a izquierdas, entonces se sustituye <SENTENCIAS>
por sus dos partes derechas, con lo que quedan las producciones:
<PROCEDURE> ::= <PROCEDURE> return ; exit
<PROCEDURE> ::= end ; exit
Con lo que se ha llegado a una recursividad a izquierdas inmediata. Aplicando el
mtodo visto anteriormente para eliminar la recursividad a izquierdas inmediata, las
producciones quedan:
<PROCEDURE> ::= end ; exit <C>
<C> ::= return ; exit <C> |
La gramtica equivalente a la dada sin recursividad a izquierdas es la siguiente:
<PROGRAMA> ::= <BLOQUE> .
<BLOQUE> ::= <SENTENCIAS> ;
<SENTENCIAS> ::= <PROCEDURE> return | end
<PROCEDURE> ::= end ; exit <C>
<C> ::= return ; exit <C> |
Anlisis Sintctico en Procesadores de Lenguaje
41
5.4.3.3 Factorizacin y sustitucin
Una gramtica puede no ser LL(1) y no ser recursiva a izquierdas. El motivo puede ser
que los conjuntos de smbolos directores no sean conjuntos disjuntos, por empezar varias
reglas de produccin por el mismo smbolo no anulable. No existe ningn algoritmo
genrico que pueda convertir las gramticas, tan solo hay recomendaciones. El mtodo de
factorizacin y sustitucin lleva implcito el concepto matemtico de sacar factor comn.
Es decir, se trata de agrupar las producciones que comienzan por el mismo smbolo no
anulable, realizar sustituciones de reglas o incluir nuevos smbolos no terminales. Dada
una produccin de la forma:
A
1
|
2
|
...
n

|


evidentemente no es LL(1) si no es anulable.
El orden en que se realizan las sustituciones es importante tenerlo en cuenta. En
general, se debe de sustituir primero el no terminal que necesita el mayor nmero de
expansiones, antes de sacar el factor comn. Bauer (1974) da en su libro [BAUE74, Pg.
75] un algoritmo para elegir el orden de sustitucin.
Algoritmo para la factorizacin:
primer paso: para cada no terminal A buscar el prefijo ms largo comn a dos
o ms alternativas de dicho no terminal.
segundo paso: Si , sustituir todas las producciones de A por:
A C |
C
1
|
2
|...|
n

Ejemplo 1: El problema if-then-else
La instruccin if-then-else expresada de la forma:
<SENT> ::= if <COND> then <SENT> else <SENT>
|if <COND> then <SENT>
Presenta el problema descrito en el apartado 4.2.5.2 impidiendo que la gramtica sea
LL(1). Sin embargo, si se aplica factorizacin y sustitucin quedara de la forma:
<SENT> ::= if <COND> then <SENT> <RESTO_IF>
<RESTO_IF>::= else <SENT> |
Pero esta sustitucin no elimina la ambigedad, dado que se puede generar ms de un
rbol sintctico. Para eliminar la ambigedad de la sentencia es necesario expresarla de la
forma vista en el apartado 4.2.5.2.
Ejemplo 2: Gramtica de expresiones aritmticas
Sea la gramtica no ambigua GNA = (VN, VT, S, P), obtenida en el apartado 2.5.3.6,
que describe expresiones aritmticas teniendo en cuenta precedencia y asociatividad. Sin
embargo no es LL(1). En primer lugar se observa varias recursividades a izquierdas
inmediatas.
Anlisis Sintctico en Procesadores de Lenguaje
42
VN = {<ELEMENTO>, <PRIMARIO>, <FACTOR>, <TERMINO>, <EXP>}
VT = {identificador, constante, (, ), ^, *, /, +, -}
S = <EXP>
y las producciones P:
<EXP> ::= <EXP> + <TERMINO> | <EXP> - <TERMINO> |
<TERMINO>
<TERMINO> ::= <TERMINO> * <FACTOR> | <TERMINO> / <FACTOR> |
<FACTOR>
<FACTOR> ::= <PRIMARIO> ^ <FACTOR> | <PRIMARIO>
<PRIMARIO> ::= - <PRIMARIO> | <ELEMENTO>
<ELEMENTO> ::= ( <EXP> ) | identificador | constante
Para resaltar la recursividad a izquierdas y manejar reglas ms compactas se aplicar
factorizacin y sustitucin, utilizando los no terminales <N1>,<N2> y <N3>:
<N1> ::= + <TERMINO> | - <TERMINO>
<N2> ::= * <FACTOR> | / <FACTOR>
<N3> ::= ^ <FACTOR> | <vaco>
Entonces las reglas de produccin anteriores quedan de la forma:
<EXP> ::= <EXP> <N1> | <TERMINO>
<TERMINO> ::= <TERMINO> <N2> | <FACTOR>
<FACTOR> ::= <PRIMARIO> <N3>
<PRIMARIO> ::= - <PRIMARIO> | <ELEMENTO>
<ELEMENTO> ::= ( <EXP> ) | identificador | constante
Ahora pueden verse claramente dos recursividades a izquierda inmediatas que
aplicando los mtodos dados se convierten en:
<EXP> ::= <TERMINO> <MAS_TERMINOS>
<MAS_TERMINOS> ::= <N1> <MAS_TERMINOS> | <VACIO>
<TERMINO> ::= <FACTOR> <MAS_FACTORES>
<MAS_FACTORES> ::= <N2> <MAS_FACTORES> | <VACIO>
El resto de las producciones quedan como estn, y si se vuelve a deshacer la
sustitucin de <N1>, <N2> y <N3> se obtiene la siguiente gramtica de expresiones
aritmticas con precedencia y asociatividad, que es LL(1).
<EXPRESION> ::= <TERMINO> <MAS_TERMINOS>
<MAS_TERMINOS> ::= + <TERMINO> <MAS_TERMINOS>
<MAS_TERMINOS> ::= - <TERMINO> <MAS_TERMINOS>
<MAS_TERMINOS> ::= <VACIO>
<TERMINO> ::= <FACTOR> <MAS_FACTORES>
<MAS_FACTORES> ::= * <FACTOR> <MAS_FACTORES>
Anlisis Sintctico en Procesadores de Lenguaje
43
<MAS_FACTORES> ::= / <FACTOR> <MAS_FACTORES>
<MAS_FACTORES> ::= <VACIO>
<FACTOR> ::= <PRIMARIO> <EXP>
<EXP> ::= ^ <FACTOR>
<EXP> ::= <VACIO>
<PRIMARIO> ::= - <PRIMARIO>
<PRIMARIO> ::= <ELEMENTO>
<ELEMENTO> ::= ( <EXPRESION> )
<ELEMENTO> ::= identificador
<ELEMENTO> ::= constante
<VACIO> ::=
Esta gramtica se puede utilizar para construir la gramtica de un lenguaje
denominado MUSIM/11, que se adjunta a continuacin:
<PROGRAMA> ::= MAIN { <BLOQUE> }
<BLOQUE> ::= <SENTENCIA> <OTRA_SENTENCIA>
<OTRA_SENTENCIA> ::= ; <SENTENCIA> <OTRA_SENTENCIA>
<OTRA_SENTENCIA> ::= <VACIO>
<SENTENCIA> ::= <ASIGNACION>
<SENTENCIA> ::= <LECTURA>
<SENTENCIA> ::= <ESCRITURA>
<ASIGNACION> ::= <VARIABLE> = <EXPRESION>
<EXPRESION> ::= <TERMINO> <MAS_TERMINOS>
<MAS_TERMINOS> ::= + <TERMINO> <MAS_TERMINOS>
<MAS_TERMINOS> ::= - <TERMINO> <MAS_TERMINOS>
<MAS_TERMINOS> ::= <VACIO>
<TERMINO> ::= <FACTOR> <MAS_FACTORES>
<MAS_FACTORES> ::= * <FACTOR> <MAS_FACTORES>
<MAS_FACTORES> ::= / <FACTOR> <MAS_FACTORES>
<MAS_FACTORES> ::= % <FACTOR> <MAS_FACTORES>
<MAS_FACTORES> ::= <VACIO>
<FACTOR> ::= <PRIMARIO> <EXP>
<EXP> ::= ^ <FACTOR>
<EXP> ::= <VACIO>
<PRIMARIO> ::= - <PRIMARIO>
<PRIMARIO> ::= <ELEMENTO>
<ELEMENTO> ::= ( <EXPRESION> )
<ELEMENTO> ::= <VARIABLE>
<ELEMENTO> ::= <CONSTANTE>
<LECTURA> ::= READ <VARIABLE>
<ESCRITURA> ::= WRITE <VARIABLE>
Anlisis Sintctico en Procesadores de Lenguaje
44
<CONSTANTE> ::= entera
<VARIABLE> ::= letra_minuscula
<VACIO> ::=
5.4.3.4 Transformacin de gramticas mediante aspectos semnticos
En algunas construcciones de los lenguajes de programacin es necesario conocer ms
informacin que la estrictamente sintctica, para ayudar a elegir el smbolo director en
cada momento, en esos casos se puede tomar informacin semntica, que habitualmente
proporcionar la tabla de smbolos o los atributos semnticos de los smbolos de la
gramtica. En el caso de los procesadores de lenguaje de una sola pasada, esto es fcil de
implementar dado que en cada instante se tiene toda la informacin del smbolo que se
est procesando.
5.4.3.4.1 Ejemplo
Sea un lenguaje de programacin tipo ALGOL con un fragmento de gramtica de la
forma:
<BLOQUE> ::= begin <ETIQ_SENT> end
<ETIQ_SENT> ::= <ETIQUETA> <SENT>
<ETIQUETA> ::= identificador :
|
<SENT> ::= while <COND> DO <SENT>
| <VARIABLE> := <EXPRESION>
| repeat <SENT> until <EXPRESION>
| for <VARIABLE> := <EXPRESION> do <SENT>
| goto constante
| case <EXPRESION> of <lista_case> end
...
<VARIABLE> ::= identificador
...
Puede observarse que, en este lenguaje, las sentencias pueden llevar etiquetas, que son
identificadores. Supongamos que se tienen las sentencias (1) y (2).
(1) aa: x:= 5
(2) yy:= 5
Cuando se analiza una sentencia y se encuentra el identificador yy puede haber duda
al elegir la alternativa correcta en la produccin <ETIQUETA>, si se elige identificador
se supone que se est en el caso (1). Si se elige vaco se supone que se est en el caso (2),
eligindose posteriormente <SENT>, <VARIABLE> e identificador. Esto produce que el
lenguaje no sea LL(1).
Para resolver este problema el lenguaje de programacin puede exigir la declaracin
obligatoria de los identificadores de tipo etiqueta, y por medio de una consulta a la tabla
de smbolos el analizador puede decidir si el identificador es una etiqueta o una variable
de otro tipo, con lo que discrimina entre instruccin con etiqueta (1) y sentencia de
Anlisis Sintctico en Procesadores de Lenguaje
45
asignacin (2). Otra solucin sera dar un smbolo terminal diferente para las etiquetas,
dado que la declaracin obligatoria permitir fcilmente al analizador lxico reconocerlas,
y se evita que la gramtica no sea LL(1).
El lenguaje Pascal estndar subsana esto definiendo etiqueta como un entero sin signo
con declaracin obligatoria, sin embargo las extensiones del Turbo Pascal de Borland
permiten el uso de identificadores como etiquetas. Los lenguajes C y C++ permiten el uso
de identificadores como etiquetas que no se declaran.
5.5 Construccin de analizadores sintcticos descendentes
En este apartado se tratan las distintas tcnicas de construccin de analizadores
sintcticos descendentes. Se pueden clasificar en cuatro grandes grupos:
a) Mtodos basados directamente en la sintaxis
b) Analizadores sintcticos descendentes recursivos.
c) Analizadores sintcticos descendentes dirigidos por tabla.
d) Analizadores sintcticos descendentes basados en mquinas de tipo 2 o de pila.
e) Analizaores sintcticos descendentes dirigidos por estructuras de datos.
5.5.1 Mtodos basados directamente en la sintaxis
Una gramtica de contexto libre puede expresar un lenguaje al igual que puede hacerlo
la notacin BNF, y los diagramas de Conway
1
. Toda gramtica reconocible mediante el
mtodo de los diagramas de Conway es LL(1).
En este epgrafe se explicarn mtodos para transcribir directamente las reglas
gramaticales a programas que realizan el anlisis sintctico. Son mtodos sencillos que lo
nico que pretenden es ayudar al lector a dar los primeros pasos entre teora e
implementacin.
5.5.1.1 Reglas de construccin de diagramas sintcticos
Las reglas para la construccin de diagramas sintcticos a partir de las producciones de
una gramtica se clasifican en: secuenciales, alternativas y repetitivas.
Operacin BNF Diagrama Conway
Secuencial AB
A B

Alternativa A | B

A
B


1
Un diagrama de Conway es un grafo dirigido donde los elementos no terminales aparecen como rectngulos, y los
terminales como crculos.
Anlisis Sintctico en Procesadores de Lenguaje
46

| B

B

{B} 1 o ms veces

B

Repetitiva
[B] 0 o ms veces

B

5.5.1.2 Traduccin de reglas sintcticas a programas
Una vez que la gramtica se ha descrito mediante un metalenguaje (BNF o diagramas
sintcticos), llega el momento de traducir esas reglas sintcticas a programas:
a) Los smbolos no terminales son procedimientos, funciones o mtodos.
b) Los smbolos terminales son tokens enviados por el analizador lxico.
c) Las reglas de produccin se traducen a estructuras de control.
1. La regla de produccin A B
1
B
2
B
3
... B
n
corresponde a una sentencia compuesta.
Ejemplo: prog: sent ; FIN
Sent ; Fin

Prog ( );
sent ( );
if el siguiente token es ; entonces
Consumir el token
else
!Error sintctico
fi
if el siguiente token es FIN entonces
Consumir el token
else
!Error sintctico
fi
FinProg
2. La regla de produccin A S
1
| S
2
| S
3
| ... | S
n
se traduce a una sentencia
multialternativa.
Anlisis Sintctico en Procesadores de Lenguaje
47
Ejemplo: Factor identificador
numero
factor
expresion ( )
NOT

factor ( ){
switch (token) {
case ID : get_token ( ); break;
case NUM : get_token ( ); break;
case NOT : get_token ( ); factor ( ); break;
case AB_PAR : get_token ( ); expresin ( );
if (token != CE_PAR) {Error: Parntesis de cierre}
else get_token ( );
break;
default : Error : Expresin no vlida.
}
}
donde token Smbolos Directores (A, S
1
| S
2
| ... | S
n
)
3. La regla de produccin A SA | o A SA | S se traduce a una sentencia
repetitiva.
Ejemplo: Expresin
expresion ;

main ( ) {
do {
expresin ( );
while (token != PUNTOYCOMA) {
!Error en expresin
get_token ( );
}
get_token( );
}while (token != EOF);
}
Ejemplo:
Expresion-simple

+
-
termino
+
-
OR
termino


Anlisis Sintctico en Procesadores de Lenguaje
48
expr_simple ( ) {
if ((token == MAS) || (token == MENOS)) {
get_token( );
}
trmino ( );
while ((token == MAS) || (token == MENOS) || (token == OR)) {
get_token( );
termino ( );
}
}
Ejemplo: Termino factor
*
/
DIV
factor
MOD
AND

trmino ( ){
factor ( );
while ((token == POR) || (token == DIV) || (token == DIV_ENT) ||
(token == MOD) || (token == AND) {
get_token( );
factor( );
}
}

Ejemplo
Sea la gramtica G = (VN, VT, P, S) donde VN = {A, B, C}, VT = {+, x, (, )}, S = A
y P son las reglas:
<A> ::= x | ( <B> )
<B> ::= <A> <C>
<C> ::= { + <A> }
Determinar:
1. Los diagramas sintcticos.
2. Ejemplos de cadenas del lenguaje
3. Escribir un programa en C que, dada una cadena, indique si es o no del lenguaje.
a) Los diagramas sintcticos se construyen en la figuras 19, 20 y 21.
Anlisis Sintctico en Procesadores de Lenguaje
49
B
x
( )
A

Figura 19: Diagrama sintctico de <A> ::= x | ( <B> )
A C

Figura 20: Diagrama sintctico <B> ::= <A> <C>
A +

Figura 21: Diagrama sintctico <C> ::= { + <A> }
En este caso particular, los diagramas sintcticos pueden combinarse para formar un
diagrama sintctico que representa a todo el lenguaje:
A +
A ( )
x

Figura 22: Diagrama sintctico que representa todo el lenguaje
b) Ejemplos de cadenas pertenecientes al lenguaje: x, (x), (x+x), (((x))), (x+x+x),
(x+x+x+x+x).
c) En este lenguaje los componentes lxicos son de una letra, por lo tanto la
programacin de un analizador sintctico es muy simple.
/* Anlisis sintctico */

#include <stdio.h>
char token, cadena[80];
int i=0;

void main(void)
{
printf("Introduce la cadena a reconocer \n");
printf("=>");
scanf("%s",cadena);
token= cadena[0];
if (a()) printf("\nCADENA RECONOCIDA");
else printf("\nCADENA NO RECONOCIDA");
}
/********************************/
int a(void)
{
Anlisis Sintctico en Procesadores de Lenguaje
50
if (token== 'x') { i+=1; token= cadena[i]; return(1); }
else if (token== '(')
if (b())
{
if (token== ')') return(1);
else return(0);
}
else return(0);
else return(0);
}
/********************************/
int b(void)
{
i+=1;
token= cadena[i];
if (a())
if (c()) return(1);
else return(0);
else return(0);
}
/********************************/
int c(void)
{
while (token== '+')
{
i+=1;
token= cadena[i];
if (!a()) return(0);
}
return(1);
}
5.5.2 Analizadores sintcticos recursivos descendentes
La condicin necesaria para que un analizador recursivo descendente opere
correctamente es que la gramtica del lenguaje fuente sea LL(1). Se considera a cada
regla de la gramtica como la definicin de una funcin o mtodo que reconocer al no-
terminal de la parte izquierda.
El lado derecho de la regla especifica la estructura del cdigo para ese mtodo o
funcin.
Los smbolos terminales corresponden a concordancias con la entrada.
Los smbolos no-terminales con llamadas a funciones o mtodos.
Las diferentes alternativas a casos condicionales en funcin de lo que se est
examinando en la entrada.
Se asume que hay una variable que almacena el token actual (el smbolo de
preanlisis). Cuando se produce una concordancia se avanza en la entrada, en caso
contrario se seala un error.
Anlisis Sintctico en Procesadores de Lenguaje
51
Implementacin de un analizador sintctico recursivo descendente
Su implementacin consta de:
Una funcin denominada Match que recibe como entrada un terminal y
comprueba si el token actual coincide con ese terminal. Si coinciden se avanza
en la entrada, en caso contrario se declara un error sintctico.
Funcion Match(terminal)
inicio
si (token-actual == terminal) entonces
obtener siguiente token
sino
error sintctico
fin
Una funcin para cada no-terminal con la siguiente estructura:
Para las reglas de la forma A
1
|
2
| ... |
n
decidir la produccin a
utilizar en funcin de los conjuntos DIRECTORES (A
i
).
Funcion A()
inicio
segun token-actual est en:
DIRECTORES (
1
): { proceder segn alternativa
1
}
DIRECTORES (
2
): { proceder segn alternativa
2
}
...
DIRECTORES (
n
): { proceder segn alternativa
n
}
Fin-segun
si token-actual no pertenece a ningn DIRECTORES (
n
)
entonces
error sintctico, excepto si existe la alternativa
A en cuyo caso no se hace nada.
Fin
Para cada alternativa
i
del no terminal, proceder analizando
secuencialmente cada uno de los smbolos que aparece en la parte derecha
tanto terminales y como no terminales.
Si es un no-terminal entonces
hacer una llamada a su funcin
Si es un terminal entonces
hacer una llamada a la funcin Match con ese terminal
como argumento.
Para lanzar el analizador sintctico se hace una llamada a la funcin que
corresponde con el smbolo inicial de la gramtica. No olvidar hacer una
llamada previa al analizador lxico para inicializar la variable con el primer
token del fichero de entrada.
Anlisis Sintctico en Procesadores de Lenguaje
52
Ejemplo: Implementar un analizador sintctico recursivo descendente para la
gramtica:
E T E
E + T E | - T E |
T F T
T * F T | / F T |
F ( E ) | num | id

Funcion Expresion() {
Termino();
Expresion Prima();
}

Funcion Expresion Prima(){
segn token-actual est en:
{TKN_MAS}:
Match(TKN_MAS);
Termino();
Expresion Prima();
{TKN_MENOS}:
Match(TKN MENOS);
Termino();
Expresion Prima();
si no est en ninguno de los anteriores entonces no hacer nada
fin_segun
}

Funcion Termino() {
Factor();
Termino Prima();
}

Funcion Termino Prima() {
Segn token-actual est en:
{TKN_POR}:
Match(TKN_POR);
Factor();
Termino Prima();
{TKN_DIV}:
Match(TKN_DIV);
Factor();
Termino Prima();
si no est en ninguno de los anteriores entonces
no hacer nada
fin_segun
}

Anlisis Sintctico en Procesadores de Lenguaje
53
Funcion Factor(){
segun token-actual est en:
{TKN_ABREPAR}:
Match(TKN_ABREPAR);
Expresion();
Match(TKN_CIERRAPAR);
{TKN_NUM}:
Match(TKN_NUM);
{TKN_ID}:
Match(TKN_ID);
si no est en ninguno de los anteriores entonces error();
fin segun
}
El anlisis recursivo descendente es la forma ms intuitiva y didctica de anlisis
sintctico descendente.
5.5.3 Analizadores sintacticos descendentes dirigidos por tabla
Se puede construir un analizador sintctico descendente predictivo no recursivo sin
llamadas recursivas a procedimientos, utilizando una pila auxiliar de smbolos terminales
y no-terminales. Para determinar qu produccin debe aplicarse en cada momento se
busca en una tabla de anlisis sintctico (parsing table), en funcin del token que se est
examinando en ese momento y del smbolo en la cima de la pila se decide la accin a
realizar.

Esquema general
Analizador
Sintctico
Tabla de anlisis
M[X,a]
Z
Y
k
Pila de smbolos
(a reconocer)
Salida
A X
X Z Y k
... b c a...
Entrada

Buffer de entrada: que contiene la cadena de componentes lxicos que se va a
analizar seguida del smbolo $ de fin de cadena.
La pila de anlisis sintctico: que contiene una secuencia de smbolos de la
gramtica con el smbolo $ en la parte de abajo que indica la base de la pila.
Una tabla de anlisis sintctico: que es una matriz bidimensional M[X,a]
con X V
NT
y a V
T
{$} . La tabla de anlisis sintctico dirige el anlisis y es
lo nico que cambia de una analizador a otro (de una gramtica a otra).
Anlisis Sintctico en Procesadores de Lenguaje
54
Salida: la serie de producciones utilizadas en el anlisis de la secuencia de entrada.
El analizador sintctico tiene en cuenta en cada momento, el smbolo de la cima de la
pila X y el smbolo en curso de la cadena de entrada. Estos dos smbolos determinan la
accin a realizar, que pueden ser:
Si X = a = $, el anlisis sintctico ha llegado al final y la cadenaha sido reconocida
con xito.
Si X = a $, el terminal se desapila (se ha reconocido ese terminal) y se avanza en
la entrada obteniendo el siguiente componente lxico.
Si X es no-terminal, entonces se consulta en la tabla M[X,a]. La tabla contiene una
produccin a aplicar o si est vaca significa un error. Si es una produccin de la
forma X Y
1
Y
2
...Yn, entonces se meten los smbolos Yn ...Y
1
en la pila (notar el
orden inverso). Si es un error el analizador llama a una rutina de error.
Algoritmo de anlisis sintctico predictivo recursivo por tabla:
Entrada: una cadenaw, una tabla de anlisis sintctico y una
gramatica G
Salida: Si w L(G), la derivacin ms a la izquierda de la cadena w,
sino una indicacin de error
Mtodo:
Como configuracin inicial se tiene en el fondo de la pila el
smbolo $ , el axioma S en la cima y la cadena w$ en el buffer de
entrada.
Iniciar el anlisis con el primer smbolo de w$
repetir
Sea X el smbolo de la cima de la pila
si X es un terminal o $ entonces
si X = token entonces
extraer X de la pila y obtener nuevo componente lxico
sino error();
sino
si M[X, token] = X Y
1
Y
2
... Y
n
entonces
extraer X de la pila
meter Y
n
...Y
2
Y
1
, con Y
1
en la cima de la pila
sino error();
hasta que X = $ (* pila vaca, se ha procesado toda la entrada*)
Cadena reconocida con xito
Como construir una tabla de anlisis sintctico
Entrada: una gramtica G
Anlisis Sintctico en Procesadores de Lenguaje
55
Salida: la tabla de anlisis sintctico, con una fila para cada
no-terminal, una columna para cada terminal y otra para $
Mtodo:
Ampliar la gramtica con una produccin S S$
Para cada produccin de la gramtica A hacer:
Para cada terminal a PRIMEROS(), aadir la produccin A
en la casilla M[A,a].
Si PRIMEROS(), aadir A en la casilla M[A,b]
b SIGUIENTES(A).
Las celdas de la tabla que hayan quedado vacas se definen como
error.
Pueden aparecer ms de una produccin en una misma casilla. Esto significara que
estamos en el caso no-determinista en el que tendramos que probar una produccin y si
sta produce un error, probar la otra, etc.
Las gramticas LL(1) garantizan que slo haya una produccin por casilla. A, el coste
del anlisis es lineal con el nmero de componentes lxicos de la entrada.
Teorema: Una gramtica es LL(1) si y slo s el algoritmo de construccin de la tabla
de anlisis sinctico no genera ninguna celda con ms de una produccin, es decir, si para
todo par de smbolos X, a tales que X V
N
y a V
T,
M[X, a] = 1
5.5.4 Construccin de analizadores sintcticos decendentes
basados en autmatas de pila
Las sentencias de un lenguaje de programacin descrito por una gramtica de tipo 2
pueden ser reconocidos por un autmata de pila. Un autmata de pila se puede representar
como en la figura 23.
cinta
CONTROL
DE
ESTADOS
token1 token2 token3 ... tokenN-1 tokenN
tope
cabeza de
lectura
apila
desapila
Pila
<Z>
<Y>
<X>
.
.
.
<A>
<S>

Figura 23: Autmata de pila

Anlisis Sintctico en Procesadores de Lenguaje
56
Algoritmo para el anlisis sintctico descendente determinista
Entrada: La cadena w$ a reconocer y una gramtica G
Salida: Si w$ L(G), la derivacin ms a la izquierda de la cadena de entrada, sino una
indicacin de error.
Mtodo: Como configuracin inicial se tiene en el fondo de la pila el smbolo $, el
smbolo inicial de la gramtica S en la cima y la cadena w$ en el buffer de entrada.
REPETIR
SEGN sea el smbolo del tope de la pila
Terminal: Si el smbolo de entrada (token) coincide con el terminal entonces
Coger siguiente token
Extraer el smbolo de la pila
Sino
Error
No-Terminal: Utilizar el smbolo no-terminal y el smbolo de entrada (token) para
determinar la produccin correspondiente
Si se encuentra la produccin X Y
1
Y
2
... Y
k
entonces
Extraer el smbolo no-terminal X
Introducir Y
k,
Y
k-1
, ... Y
1
en la pila (Y
1
estara en el tope de la pila)
Sino
Error
HASTA (Pila vaca o error)
Si la pila est vaca entonces
Aceptar la cadena de entrada
Sino
Error

Para implementar este algoritmo, el lenguaje no tiene que ser recursivo obligatoriamente.
5.5.5 Analizadores sintcticos descendentes deterministas
dirigidos por estructuras de datos
Fueron propuestos por Wirth [WIRT76, captulo 5] con el objetivo de construir
analizadores genricos.
Las estructuras de datos basadas en diagramas sintcticos pueden ser utilizadas por un
analizador sintctico descendente recursivo genrico que analizar sintcticamente
cualquier conjunto de reglas gramaticales LL(1).
5.5.5.1 Traduccin de reglas sintcticas a estructuras de datos
Se puede suponer que una gramtica est formada por un conjunto determinista de
grafos sintcticos. Cada nodo se puede representar por
Anlisis Sintctico en Procesadores de Lenguaje
57

Smbolo
Alternativa Sucesor

El smbolo es un registro variante:
a) Smbolo terminal
b) Smbolo no terminal, que es un puntero a la estructura de datos que representa
dicho smbolo no terminal.
El campo Sucesor se utiliza para apuntar al siguiente elemento en la regla gramatical,
mientras que el campo Alternativa se utiliza para apuntar a las alternativas dadas por el
smbolo |.
Una estructura de datos adecuada est dadad por las siguientes declaraciones en C:
typedef struct reglarec
{ struct reglarec *sucesor *alternativa;
union
{ Token nombre;
struct reglarec *regla;
} attr;
} Reglarec;
Las reglas equivalentes a las producciones se muestran en las figuras 24, 25 y 26.
S
1
S
2
S
3
S
n

Figura 24: Regla secuencial
S
1
S
2
S
3
S
n
NIL

Figura 25: Regla alternativa
S
vaco
NIL

Figura 26: Regla repetitiva
Anlisis Sintctico en Procesadores de Lenguaje
58
Ejemplo:
Aplicando las reglas de traduccin anteriores a la gramtica vista en la seccin
anterior:
<A> ::= x | ( <B> )
<B> ::= <A> <C>
<C> ::= { + <A> }
la estructura de datos es la que se muestra en la siguiente figura.
(
NIL
+
NIL
vaco
NIL
)
NIL
x
A
NIL NIL

5.5.5.2 Construccin de analizadores sintcticos descendentes genricos
dirigidos por estructuras de datos
Para llevar a cabo esta tarea, es preciso escribir un traductor de reglas sintcticas a
estructuras de datos por lo cual hay que hacer:
a) Definir una nueva notacin equivalente a la BNF para introducir los datos al
ordenador.
b) Traducir las producciones segn las reglas del apartado5.5.4.1
c) Construccin del analizador sintctico.
5.6 Tratamiento de errores sintcticos
Los errores en la programacin pueden ser de los siguientes tipos:
Lxicos: producidos al escribir mal un identificador, una palabra clave, un
operador, etc.
Sintcticos: producidos por una expresin aritmtica o parntesis no
equilibrados, etc.
Semnticos: producidos como consecuencia de la aplicacin de un operador a
un operando incompatible, etc.
Para el tratamiento de los errores sintcticos, un gestor de errores debe proporcionar
las siguientes funciones:
Determinar si el programa es sintcticamente correcto.
Proporcionar un mensaje de error significativo.
parser error: linea 10 columna 4, simbolo encontrado f smbolo esperado ;
Anlisis Sintctico en Procesadores de Lenguaje
59
Reanudar el anlisis tan pronto como sea posible. Sino podemos perder
demasiada parte de la entrada.
Evitar errores en cascada, un error genera una secuencia de sucesivos errores.
Para evitarlo se debe consumir (ignorar) parte de la entrada.
Realizar una correccin (reparacin) del error. El analizador intenta inferir un
programa correcto de uno incorrecto. El nmero de tokens que deben ser
insertados, borrados o cambiados debe ser mnimo. Esto es extremadamente
complicado ya que adivinar la intencin del programador no siempre es
posible. Solamente sera til para situaciones sencillas como: falta ;.
Los mtodos de recuperacin de errores suelen ser mtodos ad-hoc, en el sentido de
que se aplican a lenguajes especficos y a mtodos concretos de anlisis sintctico
(descendente, ascendente. etc), con muchas situaciones particulares.
Existen varias estrategias para corregir errores, una vez detectados:
Recuperacin en modo de alarma (Panic mode ): Consiste en ignorar el resto de la
entrada hasta llegar a una condicin de seguridad. Una condicin tal se produce
cuando es posible encontrar un token especial (por ejemplo un ; o un END).A
partir de este punto se sigue analizando normalmente.
Ejemplo: Sea el siguiente bloque de sentencias de asignacin
aux = a[i]
a[i] = a[j];
a[j] = aux;



id = id [ id ] id [ id ] = id [ id ] ; id [ id ] = id ;
Error
Token especial utilizado para continuar
la compilacin a partir de l

Recuperacin a nivel de frase: Intenta recuperar el error una vez descubierto. En el
caso anterior, por ejemplo, podra haber sido lo suficientemente inteligente como
para insertar el token ;. Hay que tener cuidado con este mtodo ya que caben
varias posibilidades.
Reglas de produccin adicionales para el control de errores: La gramtica se
puede aumentar con las reglas que reconocen los errores ms comunes. En el caso
anterior, se podra haber puesto algo como:
sent_errnea sent_sin_acabar sentencia_acabada
sentencia_acabada sentencia ;
sent_sin_acabar sentencia
Esto permite un mayor control en ciertas circunstancias.
Correccin Global : Dada una secuencia completa de tokens a ser reconocida, si
hay algn error por el que no se puede reconocer, se encontrara la secuencia
completa ms parecida que s pueda ser reconocida. Es decir, el analizador
Anlisis Sintctico en Procesadores de Lenguaje
60
sintctico le pide toda la secuencia de tokens al lxico, y lo que hace es devolver lo
ms parecido a la cadena de entrada pero sin errores, as como el rbol que lo
reconoce.
Recuperacin de errores en Analizadores Sintcticos Descendentes Recursivos
Una forma estndar de recuperacin de errores en los ASD recursivos se denomina
modo de alarma. El nombre deriva del hecho de que, en situaciones complejas, el
manejador de errores consumir un nmero posiblemente grande de tokens en un intento
de hallar un lugar para reanudar el anlisis sintctico (en el peor de los casos incluso
puede consumir todo el resto del programa). Sin embargo, cuando se implementa con
cuidado, puede ser un mtodo para la recuperacin de errores interesante. Posee la ventaja
de que virtualmente asegura que el analizadorn sintctico no caiga en eun bucle infinito
durante la recuperacin de errores.
El mecanismo bsico del modo de alarma consiste se basa en:
Proporcionar a cada procedimiento (cada no-terminal de la gramtica) un
parmetro extra, un conjunto de tokens de sincronizacin.
Segn se va efectuando el anlisis, los tokens que pueden funcionar como tokens
de sincronizacin se agregan segn se vayan realizando las llamadas.
Si se encuentra un error, el analizador explora hacia delante, desechando tokens
hasta encontrar uno que pertenezca al conjunto reanudndose as el anlisis.
Las cascadas de error se evitan al no generar nuevos errores mientras tiene lugar
esta exploracin.
Qu tokens se agregan al conjunto de sincronizacin en cada punto del anlisis sintctico ?
El conjunto de SEGUIDORES.
El conjunto de INICIALES para evitar que el manejador de errores omita
tokens importantes que inicien nuevas construcciones (sentencias,
expresiones). Tambin son importantes puesto que permiten que un analizador
sintctico descendente recursivo detecte pronto los errores en el anlisis
sintctico.

Ejemplo: Sea la gramtica de expresiones:
exp term exp
exp opsuma term exp |
opsuma + | -
term factor term
term opmult factor term |
opmult *
factor ( exp ) | numero
Anlisis Sintctico en Procesadores de Lenguaje
61
Adems del procediemiento Match y los correspondientes a cada smbolo no terminal
se aaden dos procedimientos nuevos: checkInput y scanTo. Esquematizado en
seudocdigo quedara de la siguiente forma:
procedimiento scanTo(synchset);
begin
while not (token in synchset {$}) do
getToken;
end scanTO;

procedimiento checkInput(FIRSTSet, FOLLOWSet);
begin
if not (token in FIRSTSet) then
error;
scanTo (FIRSTSet FOLLOWSet);
end if;
end checkInput;

procedimiento exp(synchset);
begin
checkInput ({(,numero}, synchset);
if not (token in synchset) then
term (synchset);
while token = + or token = - do
match (token);
term (synchset);
end while;
checkInput(synchset, {(,numero});
end if;
end exp;

procedimiento factor(synchset);
begin
checkInput ({(,numero}, synchset);
if not (token in synchset) then
case token of
( : match (();
exp ({)});
match ());
numero:
match(numero);
else error;
end case;
checkInput(synchset, {(,numero}); end if;
end factor;
checkInput es llamado dos veces en cada procedimiento:
Para verificar que un token en el conjunto Iniciales sea el token siguiente en la
entrada.
Para verificar que un token en el conjunto Siguiente sea el token siguiente en la
salida.
En general synchset se pasa en las llamadas recursivas con nuevos tokens de
sincronizacin agregados de manera apropiada.
Anlisis Sintctico en Procesadores de Lenguaje
62
Para obtener los mejores mensajes de error y recuperacin de errores, toda prueba
de token se debe examinar por la probabilidad de que una prueba ms general o
ms temprana mejore el comportamiento del error.

Conclusin
Wirth especifica las caractersticas de un buen analizador sintctico:
1. Ninguna sentencia debe dar lugar a que el analizador sintctico pierda el
control.
2. Todos los errores sintcticos deben de ser detectados y sealados.
3. Los errores muy frecuentes e imputables a verdaderos fallos de comprensin o
descuido del programador, habrn de ser diagnosticados correctamente y
habrn de evitar tropiezos adicionales del compilador (los llamados mensajes
no genuinos o de rebote). Esta tercera caracterstica es la ms difcil de lograr,
ya que incluso compiladores de gran calidad emiten dos o ms mensajes para
un determinado error.
Anlisis Sintctico en Procesadores de Lenguaje
63

TEMA 6 ANLISIS SINTCTICO ASCENDENTE


6.1 INTRODUCCIN
Se denominan analizadores sintcticos ascendentes (en ingls botton-up) porque
pretenden construir un rbol sintctico para una determinada cadena de entrada,
empezando por las hojas y constituyendo el rbol hasta llegar a la raz.
Tambin se puede considerar este proceso como la reduccin (en contraste con
produccin) de una cadena de smbolos al smbolo inicial de la gramtica, es decir, una
derivacin en sentido inverso. En cada paso del proceso, una cadena que coincida con la
parte derecha de la produccin se reemplaza por el smbolo no terminal de la parte
izquierda de dicha produccin.
6.2 Analizadores LR
Un analizador sintctico ascendente utiliza una pila explcita para realizar un anlisis
sintctico, de manera semejante a como lo hace un analizador sintctico descendente no
recursivo. La pila de anlisis sintctico contendr tanto tokens como smbolos no
terminales, y tambin alguna informacin de estado adicional. La pila est vaca al inicio
del anlisis y al final de dicho anlisis contendr el smbolo inicial de la gramtica, si el
programa de entrada es correcto.
Los analizadores LR reconocen lenguajes realizando dos operaciones cargar(shift) y
reducir (reduce). Lo que hacen es leer los tokens de la entrada e ir cargndolos en una
pila, de forma que se puedan explorar los n tokens superiores que contiene sta y ver si se
corresponden con la parte derecha de alguna de las reglas de la gramtica.
Si es as se realiza una reduccin, consistente en sacar de la pila esos n tokens y en su
lugar colocar el smbolo que aparezca en la parte izquierda de esa regla. En caso
contrario, se carga en la pila el siguiente token y una vez hecho esto se vuelve a intentar
una reduccin.
6.2.1 Ejemplo de anlisis ascendente con retroceso
Dada la gramtica
S a B c D e [1]
B B b [2]
B b [3]
D d [4]
Anlisis Sintctico en Procesadores de Lenguaje
64
comprobar si la cadena abbcde pertenece al lenguaje que describe.
Los contenidos de la pila en las sucesivas etapas del anlisis sern:

a
B
c
D
e
10
a
B
c
D
9
S
11
a
B
c
d
8
a
B
c
7
a
B
6
a
B
b
5
a
B
4
a
b
3
a
2 1

1. Inicialmente la pila est vaca.
2. Se lee el token a y se carga en la pila.
3. Se lee el token b y se carga en la pila
4. Se reduce b a B utilizando la regla [3] de la gramtica.
5. Se lee el token b y se carga en la pila.
6. Se reduce Bb a B utilizando la regla [2].
7. Se carga c en la pila.
8. Se carga d en la pila.
9. Se reduce d a D utilizando la regla [4].
10. Se carga e en la pila.
11. Se reduce aBcDe a S utilizando la regla [1].
Se ha conseguido reducir la cadena de entrada al smbolo inicial S, por lo tanto, la
sentencia pertenece al lenguaje generado por la gramtica dada. A la hora de hacer una
reduccin, hay que tener cuidado al elegir la regla a utilizar. Si en el paso 6 del ejemplo
anterior se hubiera reducido utilizando la regla [3] en lugar de la [2] nos habra quedado
en la pila:

a
B
B
c
D
10
a
B
B
c
9
a
B
B
c
8
a
B
B
7
a
B
6
a
B
b
5
B
c
d D
e

Anlisis Sintctico en Procesadores de Lenguaje
65
y no se podra seguir adelante a partir de aqu porque en la gramtica no existe ninguna
regla que se ajuste a esa combinacin de smbolos.
Para que no se presente este problema, cada vez que vaya a hacer una reduccin, el
analizador sintctico ascendente tiene que saber reconocer las reglas de produccin que
no bloquean el anlisis y producen el retroceso (backtracking). Las gramticas LR poseen
la propiedad de realizar el anlisis ascendente sin retroceso.
6.3 Gramticas y analizadores LR(k)
Una gramtica que puede ser analizada por un analizador LR mirando hasta k smbolos
de entrada por delante (lookaheads) en cada movimiento, se dice que es una gramtica
LR(k).
Los analizadores LR(k) realizan eficientemente el anlisis ascendente sin retroceso y
pueden ser generados para la mayora de las gramticas libres de contexto. La L de su
nombre indica que se lee la entrada de izquierda a derecha (Left-to-right); la R, que el
anlisis se realiza aplicando derivaciones ms a la derecha en sentido inverso (Rightmost)
y k es el nmero de smbolos de entrada por delante (lookaheads) que mira elanalizador
para tomar decisiones. Si no se especifica k se asume que k=1.
Los analizadores que utilizan la tcnica LR pueden analizar un nmero mayor de
gramticas que los analizadores descendentes y adems pueden detectar un error
sintctico tan pronto como es posible hacerlo cuando se evala la entrada de izquierda a
derecha.
Su principal desventaja es que cuesta demasiado trabajo construirlos. Se necesitan
herramientas especializadas, generadores de analizadores LR como yacc, que a partir de
la descripcin de una gramtica libre de contexto, produzcan automticamente el
analizador para el lenguaje descrito por esa gramtica.
Las tres tcnicas ms comunes para construir tablas de anlisis LR son:
SLR (LR simple): Es la ms fcil de implementar, pero la menos potente de las
tres. Puede no ser capaz de producir la tabla para algunas gramticas que los otros
mtodos s pueden tratar.
LR cannico: Es la ms potente y la ms cara de las tres.
LALR (Lookahead LR): Tiene un costo y una potencia intermedia entre los dos
anteriores. El mtodo LALR funciona con las gramticas de la mayora de los
lenguajes de programacin y, con un poco de esfuerzo, se puede implantar en
forma eficiente.
6.4 Estructura y funcionamiento de un analizador LR
Un analizador LR consta de un programa analizador LR, una tabla de anlisis, y una
pila en la que se van cargando los estados por los que pasa el analizador y los smbolos de
la gramtica que se van leyendo. Se le da una entrada para que la analice y produce a
partir de ella una salida.
Anlisis Sintctico en Procesadores de Lenguaje
66
Lo nico que cambia de un analizador a otro es la tabla de anlisis, que consta de dos
partes: una funcin accion que se ocupa de las acciones a realizar (decide qu hacer a
continuacin) y un funcin goto, que se ocupa de decidir a qu estado se pasa a
continuacin. El resto es igual para todos los analizadores LR. El programa de anlisis lee
caracteres del buffer de entrada, de uno en uno. Utiliza una pila para almacenar una
cadena de la forma S
0
X
1
S
1
X
2
S
2
...X
m
S
m
, donde S
m
es lo que est en su parte ms alta.
Cada X
i
es un smbolo de la gramtica y cada S
i
un smbolo que representa un estado.
Los smbolos de estado resumen toda la informacin contenida en la pila por debajo de
ellos. La combinacin del estado situado en lo ms alto de la pila y el smbolo de entrada
a leer (aquel en el que est situada la cabeza de lectura) se usan como ndices en la tabla
de anlisis para determinar qu se debe hacer a continuacin.
De una forma esquemtica, un analizador LR puede representarse en la figura 27.

PROGRAMA
ANALIZADOR
LR
SALIDA
a
1
a
i
$
accin goto TABLA DE ANLISIS
S
0
X
m-1
S
m-1
X
m

S
m

PILA
...

Figura 27: Esquema de un analizador LR
El smbolo $ seala el final de la cadena de entrada.
El programa de anlisis determina qu estado est en la parte superior de la pila y cul
es el smbolo de entrada a leer. Entonces consulta la accin correspondiente de la tabla
accin [S
m
, a
i
], que es la entrada en la parte de accin de la tabla de anlisis para el
estado S
m
y el smbolo de entrada a
i
. Puede tener cuatro valores:
1. cargar (shift) S, siendo S un estado.
2. reducir (reduce) utilizando una regla de produccin.
3. aceptar (accept).
4. error.
La funcin goto toma como argumentos un estado y un smbolo de la gramtica y
produce un estado. Es la funcin de transicin de un autmata finito (AF). El estado
inicial de este AF es el que est puesto inicialmente en la pila.
Una configuracin de un analizador LR es un par cuyo primer componente es el
contenido de la pila y el segundo el trozo de entrada que queda sin leer:
(S
0
X
1
S
1
X
2
S
2
...X
m
S
m
, a
i
a
i+1
...a
n
$)
Anlisis Sintctico en Procesadores de Lenguaje
67
El siguiente movimiento del analizador se determina mediante a
i
y S
m
. Las
configuraciones que resultan de cada uno de los cuatro tipos de movimiento son:
1. Si accin [S
m
, a
i
] = cargar S, el analizador ejecuta un movimiento de carga, dando
como resultado la configuracin:
(S
0
X
1
S
1
X
2
S
2
...X
m
S
m
a
i
S, a
i+1
...a
n
$)
Se ha cargado tanto el smbolo de entrada a
i
como el siguiente estado S (que es
goto[S
m
,a
i
]) en la pila. a
i+1
pasa a ser el smbolo de entrada a leer. El analizador est ahora
en el estado S.
2. Si accin [S
m
, a
i
] = reducir por A b, el analizador ejecuta una reduccin
utilizando la regla A b dando la configuracin:
(S
0
X
1
S
1
X
2
S
2
...X
m-r
S
m-r
AS, a
i
a
i+1
...a
n
$)
donde S ser goto[S
m-r
, A] y r la longitud de la cadena de smbolos b. El analizador
primero saca de la pila 2r smbolos, r estados (S
m-r+1
...S
m
) y r smbolos de la gramtica
(X
m-r+1
...X
m
), que tendrn que ser los mismos que componen la cadena b (parte derecha de
la regla por la que se reduce) y despus coloca en la pila el no terminal A (parte izquierda
de la regla, que pasar a sustituir a la parte derecha en la pila) y S, que ser el estado al
que pase el analizador tras reducir. No cambia el smbolo de entrada a leer.
Una vez que se ha llevado a cabo la reduccin se deben ejecutar las acciones
semnticas correspondientes a la regla por la que se redujo, que son trozos de programa
que producen la salida del analizador LR para la entrada que se est leyendo. Cada regla
de produccin puede llevar una o varias acciones asociadas, que se ejecutarn cada vez
que se reduzca, es decir, cada vez que en la entrada aparezca una secuencia de smbolos
que se ajuste a su parte derecha.
3. Si accin [Sm, ai] = aceptar, el anlisisse ha terminado con xito.
4. Si accin [Sm, ai] = error, se ha descubierto un error. El analizador entonces
llamar a la rutinas de recuperacin de errores.
6.4.1 Ejemplo
Sea la gramtica G:
1. E E + T
2. E T
3. T T * F
4. T F
5. F ( E )
6. F id
Anlisis Sintctico en Procesadores de Lenguaje
68
La tabla de anlisis LR para ella ser:
Accin Goto
ESTADO id + * ( ) $ E T F
0 s5 s4 1 2 3
1 s6 acc
2 r2 s7 r2 r2
3 r4 r4 r4 r4
4 s5 s4 8 2 3
5 r6 r6 r6 r6
6 s5 s4 9 3
7 s5 s4 10
8 s6 s11
9 r1 s7 r1 r1
10 r3 r3 r3 r3
11 r5 r5 r5 r5

si indica cargar y apilar el estado i, rj reducir mediante la produccin j y acc aceptar.
Los cuadros en blanco corresponden a situaciones errneas.
En la parte goto de la tabla slo aparecen las transiciones para los smbolos no
teminales. Esto es debido a que las transiciones de los terminales se producen
inmediatamente despus de cargarlos, es decir, cada carga de la parte accion de la tabla
lleva consigo una accin goto, ya que, tras cargar el smbolo, el analizador pasa a otro
estado. Por ejemplo, accin[1, +] = s6 indica que si el analizador est en el estado 1 y
aparece en la entrada un signo +, se deber cargar este en la pila y a continuacin pasar al
estado 6, por lo que se podra decir que goto[1, +] = 6.
6.5 Algoritmo de anlisis LR
Recibe como entrada una cadena w y una tabla de anlisis LR con las funciones accin
y goto para una gramtica determinada G y produce como salida un anlisis ascendente
para la cadena w si sta pertenece al lenguaje generado por G, o error en caso contrario.
Anlisis Sintctico en Procesadores de Lenguaje
69
Inicialmente, el analizador tiene en su pila S
0
, el estado inicial, y w$ en el buffer de
entrada. Entonces ejecuta el programa siguiente hasta llegar a error o accept.
hacer que p apunte al primer smbolo de w$;
repeat forever
begin
Sea S el estado situado en la parte superior de la pila y a
el smbolo al que apunta p;
if accion[S,a] = cargar S' then
begin
Cargar a y luego S' en la pila;
Hacer que p apunte al siguiente smbolo;
end
else if accion[S,a] = reducir por A then
begin
Sacar 2*|| smbolos de la pila;
Sea S' el estado situado en la parte alta de la pila;
Poner A y luego goto[S',A] en la pila;
Ejecutar las acciones semnticas de A ;
end
else if accion[S,a] = aceptar then
return
else error()
end

6.6 Gramticas LR(k) - LL(k)
Existen diferencias significativas entre estos dos tipos de gramticas. En primer lugar,
las gramticas LL(k) dan lugar a analizadores descendentes, mientras que los analizadores
que utilizan la tcnica LR son ascendentes. En segundo lugar, para que una gramtica sea
LR(k), tiene que ser posible reconocer la parte derecha de una regla de produccin cuando
aparece en la pila, despues de haber ledo toda esa parte derecha y k smbolos ms de
entrada (lookaheads). Esta restriccin es mucho menor que la de las gramticas LL(k), en
las que tenemos que ser capaces de reconocer el uso de una regla de produccin viendo
slo los primeros k smbolos de su parte derecha. Por lo tanto, las gramticas LR(k)
pueden describir un mayor nmero de lenguajes que las gramticas LL(k).
Diferencias entre las gramticas LR y LL en funcin de un conjunto de criterios:
1. Sencillez de implementacin: Los analizadores LL son sencillos de implementar
manualmente; los LR son complejos y requieren el uso de herramientas.
Anlisis Sintctico en Procesadores de Lenguaje
70
2. Sencillez de funcionamiento (depuracin): El proceso descendente (derivaciones por
la izquierda) es ms intuitivo y fcil de depurar que el ascendente (en orden inverso, y
con derivaciones por la derecha).
3. Expresividad: Toda gramtica LL(1) es LR(1). La expresividad de las gramticas LR
es, en cualquier caso, mayor que las de las gramticas LL
4. Reglas semnticas: Los esquemas de traduccin, gramticas atribuidas y definiciones
dirigidas por sintaxis de gramticas LR emplean atributos sintetizados; las que
emplean LL ofrecen tanto sintetizados como heredados.
5. Recuperacin de errores: Los analizadores LL poseen informacin de las
construcciones que se estn reconociendo, mientas que los LR poseen informacin de
aqullas que ya estn reconocidas; la informacin de los analizadores LL es ms til
para recuperar los posibles errores.
7. Tamao de las tablas de anlisis: Ficher y Leblanc demostraron que los
compiladores LR requieren tablas de aproximadamente el doble tamao que los
compiladores LL.
6.7 Tratamiento de Errores Sintcticos
Cuando se detecta un error sintctico, es deseable que se pueda continuar procesando
el programa. Esta necesidad conlleva la implementacin de algn mecanismo de
recuperacin o reparacin de errores.
En el caso de la recuperacin de errores, el analizador debe seguir procesando el
programa descartando la entrada errnea. Dependiendo de la calidad de la recuperacin,
los siguientes errores stos podrn ser reales o errores en cascada (producidos por una
mala recuperacin).
En la reparacin de errores se trata de modificar la entrada para conseguir
construcciones vlidas donde exista un error sintctico. Este proceso suele estar basado
en modificar o aadir la secuencia de tokens bajo anlisis, para poder seguir procesando.
Los procesadores que implementan reparacin de errores.
a) Informan al programador de los errores detectados.
b) Generan un programa con el resultado de las reparaciones efectuadas.
c) Permiten ejecutar ste, aunque pueda suponer un comportamiento no deseado.
Recuperacin de errores en analizadores sintcticos ascendentes
Un analizador sintctico ascendente encontrar un error cuando se detecte una entrada
en blanco (o de error) en la tabla de anlisis sintctico. Los errores deberan detectarse
tan pronto como fuera posible, ya que los mensajes de error pueden ser ms
especficos y significativos. De esta forma una tabla de anlisis sintctico debera tener
tantas entradas en blanco como fuera posible, sin embargo, esta meta est en conflicto
con el objetivo de reducir el tamao de la tabla de anlisis sintctico, y con el
Anlisis Sintctico en Procesadores de Lenguaje
71
algoritmo particular utilizado, que puede afectar la capacidad del analizador sintctico
para detectar los errores de inmediato.
Mediante la recuperacin de errores en modo de alarma se puede conseguir una
recuperacin de errores razonablemente buena en analizadores ascendentes eliminado
smbolos gramaticales de la pila de analizador o de la entrada, o bien de ambas.
Una implementacin muy empleada en parsers LR es la siguiente:
1. Extraer estados de la pila de anlisis sintctico hasta encontrar un estado que tenga
un valor en la tabla salto (existe una reduccin).
2. Si existe una accin legal sobre el token de entrada actual desde uno de los estados
de la tabla salto, insertar ese estado en la pila y reiniciar el anlisis.
3. Si no existe accin legal con el token de entrada actualdesde uno de los estados,
eliminar tokens hasta que se d esta situacin y reanudar el proceso.
6.8 Generadores de Procesadores de Lenguajes
Los analizadores sintcticos ascendentes son difciles de implementar manualmente
puesto que la construccin de las tablas de anlisis LR es una tarea ardua y tediosa.
Aplicando la definicin utilizada en [GH98] podemos decir que:
Un generador de procesadores es un programa que transforma una especificacin en
un procesador para el lenguaje de programacin descrito en la especificacin.
6.8.1 Estructura
La siguiente figura muestra cmo a partir de la entrada al generador de procesadores,
un metalenguje, que es un lenguaje que describe otro lenguaje, se genera un procesador
de forma automtica.

Metalenguaje
Generador de
Procesadores
Procesador
de Lenguaje
Programa Fuente Programa Objeto

Figura 28: Esquema general de un generador de procesadores de lenguajes
En el proceso de generacin de procesadores, se consideran tres tipos de tiempos.

Anlisis Sintctico en Procesadores de Lenguaje
72
Tiempo de generacin.
Cuando el generador es ejecutado. A partir de una entrada en forma de metalenguaje,
se obtiene como salida el procesador de lenguaje correspondiente.
Tiempo de compilacin.
Cuando la salida obtenida en tiempo de generacin es ejecutada. Se parte de un
programa fuente como entrada al procesador de lenguaje y se traduce a un programa
objeto.
Tiempo de ejecucin.
Cuando el cdigo generado por el procesador de lenguaje es ejecutado por el sistema
operativo de la mquina.
6.8.2 Caractersticas
Los generadores de procesadores de lenguajes se disean generalmente con un modelo
particular de lenguaje, as la herramienta est ms indicada para generar procesadores de
lenguajes similares a ese modelo.
Para conseguir un software satisfactorio, los generadores ocultan los detalles de la
implementacin al usuario. Debido a que casi todas las herramientas generan solamente
una parte del procesador, es muy importante que pueda ser integrada con las diferentes
partes del procesador que no hayan sido generadas. Se puede decir que el grupo ms
importante de generadores automatizan la fase de anlisis del procesador, la fase de
sntesis se construye de forma manual.
Podramos destacar las siguientes caractersticas.
Metalenguajes
Un metalenguaje describe a otro lenguaje o algunos aspectos de otro lenguaje. Existen
diversos metalenguajes que son muy conocidos, las expresiones regulares para describir
tokens, la notacin BNF (Backus-Naur-Form) para describir la sintaxis de los lenguajes
de programacin o las gramticas atribuidas para describir la semntica de los lenguajes
aadiendo atributos y funciones semnticas a la notacin BNF. Algunos metalenguajes
pueden usarse para describirse a ellos mismos.
Principales propiedades que debera tener un metalenguaje:
Fcil de leer, aprender y usar.
Integrado, que tenga notaciones similares para las funciones a travs de las
diferentes partes del generador. Por ejemplo, el smbolo = debera ser usado
como operador de asignacin para la parte de las expresiones regulares, la
sintaxis y las funciones semnticas.
De forma anloga a los lenguajes de programacin, no debera incorporar
construcciones que permitan cometer errores fcilmente.


Anlisis Sintctico en Procesadores de Lenguaje
73
Funcionalidad
Las caractersticas funcionales de un generador de procesadores es describir cmo
trabaja el sistema, sus capacidades y los aspectos normales del software como la
extensibilidad, robustez e integracin.
Para conseguir una buena funcionalidad es necesario que:
La herramienta sea robusta. Los errores cometidos por el usuario deben ser
gestionados de forma adecuada.
La herramienta sea eficiente en tiempo y espacio. Es necesario utilizar las
estructuras adecuadas en todo momento.
La herramienta se pueda extender e integrar con las diferentes partes del
procesador.
Documentacin
La documentacin describe la herramienta, y al igual que para cualquier aplicacin
software a gran escala, se divide en dos partes: el manual de usuario y la documentacin
del sistema donde se describen los aspectos tcnicos de la herramienta. En este caso, los
lmites entre los dos est un poco confuso ya que con los detalles tcnicos es necesario
conocer como se usan, y para usarlo es necesario conocer los aspectos tcnicos.
6.8.3 Clasificacin
Los generadores de procesadores se encuentran en general separados en distintas fases.
De forma anloga a la divisin establecida en la fase de anlisis (front-end) de un
compilador, se establece la siguiente clasificacin:
Generadores de analizadores lxicos (scanner)
Generadores de analizadores sintcticos (parser)
Generadores de analizadores semnticos
Para la fase de sntesis (back-end) de un compilador se estn llevando a cabo
investigaciones para la obtencin de generadores de generadores de cdigo.
El la figura 29 se indica cmo un generador de procesadores puede ser parametrizado.
Las cajas rectangulares muestran cada una de las fases estndar de un procesador y las
cajas redondeadas muestran los correspondientes generadores. La fase de anlisis se
encuentra agrupada porque por lo general existe un nico fichero de entrada conteniendo
toda la informacin necesaria para generar la fase de anlisis (front-end) de un
compilador.
La mayor parte de los generadores existentes hoy en da, no son tan ambiciosos como
para poder resolver todos los problemas que afronta el diseador de procesadores. De
hecho, de todas las tareas, algunas de ellas se prestan ms fcilmente a automatizacin
que otras. Dentro de la fase de anlisis, por ejemplo, las tareas de anlisis lxico y
anlisis sintctico han sido estudiadas ampliamente y pueden ser formalizadas, por lo que
parece obvio que sea posible construir herramientas que las automaticen. Sin embargo, la
tarea de anlisis semntico es mucho ms compleja de automatizar, no existen
mecanismos formales tan populares como en el caso anterior, por lo que es difcil
encontrar herramientas que puedan ayudar en esta tarea.
Anlisis Sintctico en Procesadores de Lenguaje
74
Como consecuencia de lo anterior, nos encontramos con multitud de herramientas que
automatizan la creacin de analizadores lxicos y sintcticos, y muy pocas que intenten
resolver de manera efectiva la automatizacin del anlisis semntico, lo cual es lgico,
puesto que esta etapa es la que ms cambia segn las circunstancias del lenguaje que
soporte el compilador.

Fase de Anlisis
Analizador
Lxico
Analizador
Sintctico
Analizador
Semntico
Optimizador

Generacin de
cdigo
Programa
Fuente
Tokens rbol
Sintctico
Cdigo
Intermedio
Programa
Objeto
Generador de
Analizadores
Sintcticos
Generador de
Analizadores
Lxicos
Generador de
Analizadores
Semnticos
Generador de
Generadores de
Cdigo
Generador Fase de Anlisis
Expresiones
regulares
Gramticas
libres de contexto
Gramticas
Atribuidas
Descripcin del
Cdigo Intermedio y
Cdigo Mquina
Metalenguajes

Figura 29: Parametrizacin de un generador de procesadores de lenguajes
Los generadores son herramientas que ayudan a construir procesadores de lenguajes
generando alguna o todas las fases del procesador de forma automtica. A partir de una
especificacin, algunos generadores construyen procesadores completos (realizan el
anlisis y la generacin-optimizacin de cdigo para mquinas reales) mientras que otros,
solamente construyen la fase de anlisis de un compilador (generan analizadores lxicos
y/o sintcticos). En el plano terico, puede parecer que un generador que construya todas
las fases del procesador ser ms potente, sin embargo, en la prctica, los generadores que
construyen nicamente analizadores lxicos y/o sintcticos estn integrados normalmente
con un lenguaje de programacin de propsito general. De esta forma, la implementacin
de estructuras de datos complejas, optimizaciones y anlisis de cdigo, son fciles de
realizar ya que se utiliza el lenguaje de programacin nativo del programador.
6.8.4 Ventajas
Las ventajas de utilizar herramientas especficas para construir procesadores, se
podran resumir en los siguientes puntos:
Atenuar la complejidad.
Favorecer su calidad
Disminuir el tiempo y los costes necesarios para su desarrollo.
Anlisis Sintctico en Procesadores de Lenguaje
75
6.8.5 Generadores de analizadores sintcticos
Un generador de analizadores sintcticos, es un programa que convierte una
gramtica en un programa (analizador sintctico) que puede reconocer el lenguaje que
describe dicha gramtica.
En la figura 30 se muestra cmo la entrada al generador es un metalenguaje
representado mediante una gramtica libre de contexto. Esta gramtica describe las
construcciones sintcticas del programa fuente que el analizador deber reconocer.

Analizador
Lxico
Analizador
Sintctico
Analizador
Semntico
Optimizador Generador de
cdigo
Tokens rbol
Sintctico
Generador de
Analizadores
Lxicos
Generador de
Analizadores
Sintcticos
Generador de
Analizadores
Semnticos
Gramtica
Libre de contexto
Generador de
Generadores de
Cdigo

Figura 30: Esquema de un generador de analizadores sintcticos
En el siguiente ejemplo se muestra la gramtica expresada en notacin BNF de un
programa para reconocer sentencias formadas por expresiones aritmticas.

Analizador
Sintctico
Generador de
Analizadores Sintcticos
Programa Sentencias
Sentencias Sentencia Sentencias | Sentencia
Sentencia SentAsig
SentAsig Identificador := Exp
Exp Exp + Term | Exp Term | Term
Term Term * Factor | Term / Factor | Factor
Factor ( Exp) | Identificador | Literal

El rbol sintctico obtenido despus de que los componentes lxicos hayan sido
analizados es el siguiente.
Anlisis Sintctico en Procesadores de Lenguaje
76

Analizador
Sintctico
(Id, X1)
(Op, :=)
(Id, a)
(Op, +)
(Id, bb)
(Op, *)
(Lit, 12)
(Punt, ;)
(Id, X2)
(Op, :=)
(Id, a)
(Op, /)
(Lit, 2)
(Op, *)
(Id, bb)
(Op, *)
(Lit, 12)
(Punt, ;)
Sentencias
Sentencias Sentencia
SentAsig
X1 := Exp ;
Exp + Term
Term * Factor Term
Factor Factor 12
a bb
SentAsig
X2 := Exp ;
Exp + Term
Term * Factor
Factor 12
bb
Sentencia
Term / Factor
Factor 2
a
Los primeros generadores de la fase de anlisis (front-end) aparecieron a principios de los
60 y hasta la actualidad se han venido desarrollando numerosas herramientas. Existen
generadores de analizadores lxicos y sintcticos, para la mayora de los lenguajes de
programacin utilizados hoy en da.
Si bien es cierto que hay un amplio nmero de herramientas pensadas para facilitar el
trabajo del diseador de lenguajes de programacin, si hay una que tenga especial
importancia por su difusin y trascendencia es YACC, Yet Another Compiler Compiler,
obtenida en 1975 [Joh75]. En principio fue creada para el sistema operativo UNIX y est
asociada con otra herramienta llamada LEX [Les75] que genera analizadores lxicos. Esta
herramienta ha sentado un precedente de tal forma que la mayora de las que han
aparecido posteriormente, se presentan siempre en comparacin con YACC.
6.8.5.1 Lex/Yacc
Existen numerosas variaciones y su influencia se deja sentir en otras muchas
herramientas de construccin de procesadores de lenguajes en uso actualmente. Aunque
la versin ms popular es la del proyecto GNU (Free Software Foundation) con las
herramientas Flex [Pax95] y Bison [DS95] existen una larga lista, citando a modo de
ejemplo las herramientas PCYACC [Pcy00] y PCLEX [Pcl00] as como las versiones
realizadas para el lenguaje Java denominadas Jlex [Ber97] y CUP (Constructor of Useful
Parsers) [Hud97].
Esta popularidad se debe en parte a que YACC se incluye de serie desde hace bastante
tiempo en el sistema operativo Unix junto con otras herramientas de programacin que
tambin se han vuelto muy populares. No obstante, es indudable que buena parte de su
xito se debe sobre todo, a las cualidades tcnicas de la aplicacin.
YACC genera analizadores ascendentes (figura 31). Estos analizadores se basan en la
construccin de tablas de anlisis LALR(1).
Anlisis Sintctico en Procesadores de Lenguaje
77

Generador de
Analizadores Sintcticos
Yacc
Prog := fun {S$..
fun := ID {$1... }
stmt := ...
Anlisis Ascendente
Tablas de anlisis
LALR(1)
Especificacin
Gramatical
+
Cdigo de Usuario
Fichero
(Cdigo en C)
s,x
s,x
Especificacin
Lxica
Generador de
Analizadores Lxicos
Lex
Programa.c
(AFD)

Figura 31: Arquitectura de Lex/YACC
El generador de analizadores sintcticos es YACC. A partir de una especificacin que
contiene la gramtica del lenguaje para el cual se desea generar el procesador y las
acciones asociadas a las alternativas de las producciones de la gramtica, genera un
analizador que ejecutar el cdigo de accin asociado a cada alternativa tan pronto como
sean reconocidos los smbolos de dicha alternativa.
6.8.5.1.1 Construccin de un Procesador de Lenguaje utilizando Lex/Yacc

Yacc
Lex
entrada.y
yytab.h
yytab.c
entrada.l lexyy.c
Compilador
C/C++
(yyparse)
(yylex)
Programa
Fuente
mi-ejecutable
Programa
Objeto

Para construir un procesador de lenguaje es necesario especificar los componentes
lxicos para el lex (entrada.l) y las reglas gramaticales para el yacc (entrada.y).
Yacc lee la descripcin gramatical a travs del fichro entrada.y y genera un alizador
sintctico o parser en el fichero yytab.c que contiene a la funcin yyparse. Al llamar a
yacc con la opcin d se genera el fichero yytab.h con la definicin de los tokens. Lex lee
la descripcin de los tokens a travs del fichero entrada.l que incluye al fichero yytab.h y
genera un alizador lxico o lexer en el fichero lexyy.c que contiene a la funcin yylex.
Finalmente el lexer y el parser son compilados y linkados juntos para formar el fichero
ejecutable. Desde la funcin main se llama a la funcin yyparse y esta se encarga de
llamar automticamente a la funcin yylex, para obtener cada uno de los tokens, y a la
funcin yyerror para el tratamiento de errores.
Anlisis Sintctico en Procesadores de Lenguaje
78
Al llamar a Yacc se pueden indicar diversas opciones
1
:
-c: Hace que el fichero generado se llame yytab.c en lugar de llevar el nombre del
fichero de entrada con la extensin .c.
-C<nombre>: Asigna al fichero generado el nombre que se indica en el argumento.
-d: produce un fichero de cabecera con las #define de los tokens de la gramtica. Su
nombre ser yytab.h.
-D<nombre>: Hace los mismo que d pero le da al fichero producido el nombre
indicado en su arguemento si lo lleva o, si no es as, el nombre del fichero de
entrada cambiando su extensin por .h.
-v: Produce un fichero llamado yy.lrt que contiene informacin acerca del AF
generado y resulta muy til para localizar y conrregir conflictos.
-V<nombre>: Hace los mismo que v pero le da al fichero producido el nombre que
aparece en su argumento o si no lo lleva, el nombre del fichero de entrada
cambiando su extensin por lrt.
-r: Muestra informacin acerca de las oparaciones que se estn realizando.

6.8.5.1.2 Smbolos
En la descripcin de una gramtica, podemos encontrarnos smbolos terminales o
tokens y smbolos no terminales o variables. Pero a la hora de construir un analizador
sintctico hay que tener en cuenta dos tipos de tokens: por una parte estn los de los
ficheros de entrada a nuestro analizador, y por otra, los que aparecen en la descripcin de
la gramtica, que representan a los anteriores. Los de los ficheros de entrada sern trozos
de estos ficheros que la funcin yylex le ir pasando a yyparse para que los agrupe en
sentencias. Cada uno de los tokens de la gramtica representa a un token del fichero de
entrada, o bien a un conjunto de ellos sintcticamente equivalentes. Por ejemplo: el token
* de la gramtica representar solamente a los smbolos del fichero a analizar, pero el
token INTEGER representar a todos los nmeros enteros que aparezcan en l.
Todos los tokens utilizados en la gramtica hacen que aparezca al principio del fichero
de salida un #define que les asignar un cdigo numrico. De esta forma, que cada vez
que yylex es llamada por yyparse tiene que devolverle el cdigo de lo que acaba de leer,
puede utilizar los nombres de los tokens en lugar de sus cdigos. Los literales de un solo
carcter no generan #define. Su cdigo ser el nmero ASCII que les corresponda.
El smbolo error es un terminal reservado para la recuperacin de errores y no debe ser
usado para ninguna otra cosa. En particular, yylex no debe devolver nunca este valor.
Todos los smbolos terminales de la gramtica deben ser declarados, excepto los
literales de un solo carcter. Los smbolos como '<>', '>=' o '<=', que son literales de ms

1
Listado de algunas opciones de la herramienta pcyacc
Anlisis Sintctico en Procesadores de Lenguaje
79
de un carcter, tienen que ser representados por un nombre (por ejemplo NE, GE, LE) y
declarados en la seccin de declaraciones (y por lo tanto, generarn una #define en el
fichero de salida). Por el contrario, los smbolos no terminales se declaran implcitamente
en las reglas de la gramtica, no es necesario declararlos de forma explcita a menos que
se quiera indicar el tipo que van a tomar sus valores semnticos.
6.8.5.1.3 Estructura del fichero de entrada
El fichero de entrada debe tener la forma:
DECLARACIONES
%%
DESCRIPCIN DE LA GRAMATICA
%%
SUBRUTINAS
Las secciones de declaraciones y subrutinas son opcionales, pero la descripcin de la
gramtica tiene que aparecer siempre. El smbolo %% es un delimitador especial que
seala el principio y el final de estas secciones. El primero de ellos debe aparecer aunque
no exista la seccin de declaraciones, en cambio no es necesario poner el segundo si no
hay cdigo adicional de las subrutinas.
Seccin de declaraciones
En esta seccin pueden aparecer dos tipos de declaraciones: declaraciones del lenguaje
C y declaraciones especficas de la herramienta. Se suele utilizar el formato :
%{
DECLARACIONES C
%}
DECLARACIONES DE YACC
Tambin pueden aparecer varios grupos de declaraciones C (cada uno con sus
correspondientes %{ y %}) intercalados con el resto de las declaraciones de Yacc, pero la
utilizacin del formato indicado har que el fichero de entrada presente una forma ms
estructurada.
Cualquier cosa encerrada entre %{ y %} pasa directamente al principio del fichero de
salida, sin ser tocado para nada por la herramienta, por tanto, si el texto escrito en estas
zonas no es correcto, los errores tendrn que ser detectados posteriormente por el
compilador del lenguaje. Como es colocado al inicio del fichero de salida, lo que suele
aparecer en esta zona son declaraciones de tipos que sern utilizados en yyparse, macros
como #include o cualquiera de las cosas que pueden estar al principio de un programa.
Tanto en esta seccin como en el resto del fichero de entrada, se pueden incluir
comentarios, que tendrn la misma estructura que en el lenguaje (/* ..*/ // )
Las declaraciones especficas de Yacc son una serie de clusulas para definir los
tokens y smbolos no terminales de la gramtica y algunas de sus propiedades. Son:
Anlisis Sintctico en Procesadores de Lenguaje
80
%union Es una estructura (similar a la union de C) en la que se indican los tipos de
los distintos valores que van a pasar por la pila del analizador generado (yyparse) durante
el anlisis sintctico, es decir, los tipos de los valores semnticos de los tokens y no
terminales que intervienen en la gramtica. Por ejemplo:
%union
{
tipo1 nombre1;
tipo2 nombre2;
tipo3 nombre3;
}
indica que la pila de yyparse contendr valores de los tipos tipo1, tipo2 y tipo3, que
ocuparn los campos nombre1, nombre2 y nombre3 de la unin respectivamente. Esto
pasa al fichero de salida transformado en:
typedef union
{
tipo1 nombre1;
tipo2 nombre2;
tipo3 nombre3;
} YYSTYPE;
Si el usuario escribiese esto directamente al principio del fichero de salida o en las
declaraciones del lenguaje C (%{ ... %}) y suprimiese la clusula %union del fichero de
entrada a, el resultado sera exactamente el mismo. YYSTYPE es el nombre del tipo de la
pila de yyparse. Si va a contener valores de un solo tipo, no es necesario utilizar la
clusula %union. Basta con poner:
%{
.
.
.
#define YYSTYPE tipo
.
.
.
%}
en la seccin de declaraciones, o bien poner la clusula #define directamente al
principio del fichero de salida. Si no se define ningn tipo de datos, por defecto el tipo de
la pila ser entero.
%token Esta clusula indica que todos los smbolos que vienen a continuacin de ella
(hasta que aparezca una nueva declaracin o %%) son tokens. Los nombres de los tokens
pueden ir separados por comas o por blancos. Tambin se puede indicar su tipo poniendo:
%token <tipo> token1 token2 token3 ...
siendo tipo uno de los campos definidos en %union (en el ejemplo anterior nombre1,
nombre2 o nombre3). Slo se puede indicar un tipo por cada declaracin %token.
Yacc asigna automticamente un nmero entero a cada token, que ser el que le
corresponda segn el cdigo ASCII si es un literal de un solo carcter o cualquier nmero
Anlisis Sintctico en Procesadores de Lenguaje
81
que se le asigne si no lo es. En este caso, si queremos asignarle un nmero concreto,
podemos indicarlo en esta declaracin, poniendo detrs del nombre del token el cdigo
que queramos que tenga, que no debe coincidir con el de ningn otro. Generalmente es
mejor dejar que sea el programa quien elija los cdigos numricos para los tokens, ya que
se ocupar de que sean todos distintos y de que no coincidan con los de los caracteres
ASCII, liberando de esta carga al programador.
%type Se utiliza para declarar el tipo de los smbolos no terminales. Su estructura es
similar a la de %token con la diferencia de que %type tiene que llevar obligatoriamente
un nombre de tipo detrs:
%type <tipo> variable1 variable2 variable3 ...
%left*, %right*, %nonassoc Estas clsulas se utilizan para declarar un token y a la
vez especificar su precedencia y asociatividad. Su sintaxis es la misma que la de %token.
Indican que todos los tokens nombrados a continuacin de ellas son asociativos de
izquierda a derecha, de derecha a izquierda o no asociativos respectivamente. Tambin
pueden especificar su tipo y su cdigo numrico como ocurra con la clusula %token.
La asociatividad de un operador OP determina cmo se asocian los usos repetidos de
ste, es decir, dada la expresin X OP Y OP Z, si se debe analizar agrupando primero X
con Y o Y con Z. %left indica asociatividad de izquierda a derecha (primero se agrupa X
con Y, es decir, se ejecutar (X OP Y) OP Z y %right de derecha a izquierda (primero se
agrupa Y con Z, es decir, se ejecutar X OP (Y OP Z)). %nonassoc indica que no hay
asociatividad, es decir, X OP Y OP Z se considerar un error de sintaxis.
La precedencia de un operador determina cmo se asocia ste con otros operadores. Si
en la seccin de declaraciones aparecen varias declaraciones de asociatividad, cada una de
ellas tendr una precedencia un nivel superior a la que la precede, es decir, si en el fichero
de entrada aparece:
%left '+' '-'
%left '*' '/'
%right '^'
la precedencia ms baja la tienen los tokens '+' y '-', la de '*' y '/' ser superior a la de los
anteriores y la precedencia ms alta de todas ser la de '^'. Dentro de una misma
declaracin de asociatividad, todos los tokens tienen la misma precedencia.
%start Esta clusula indica que lo que viene a continuacin de ella es el smbolo
inicial de la gramtica. Tiene que ser necesariamente un no terminal, si no lo es se
producir un error. Si no aparece esta clusula en el fichero de entrada, se toma como
smbolo inicial el no terminal que aparece en la parte izquierda de la primera regla
especificada en la descripcin de la gramtica.
Cualquier smbolo que aparezca en una regla gramatical y no haya sido declarado en
esta seccin se considera no terminal (excepto los tokens literales de un solo carcter).
Descripcin de la gramtica
Esta parte del fichero de entrada es la nica que tiene que aparecer necesariamente.
Contiene la descripcin de la gramtica, que deber ir expresada de forma que Yacc
pueda entenderla.
Anlisis Sintctico en Procesadores de Lenguaje
82
Los smbolos, tanto terminales como no terminales, se representan solamente con su
nombre. Por convenio se escriben en maysculas los tokens y en minsculas los no
terminales, para distinguirlos. Los smbolos terminales de un slo carcter (signos de
puntuacin, parntesis, etc.) pueden ser representados por ese mismo carcter encerrado
entre comillas, por ejemplo, el token * se representar mediante '*'.
Las reglas de produccin sern de la forma:
parte_izquierda : parte_derecha ;
parte_izquierda es el smbolo no terminal descrito por esa regla y parte_derecha es un
conjunto de smbolos terminales y/o no terminales. Por ejemplo:
exp : exp '+' exp ;
indica que si en la entrada del analizador aparece una expresin seguida de un signo '+' y
otra expresin, todo esto debe ser agrupado para formar una nueva expresin.
Los espacios en blanco (incluyendo tabuladores y marcas de fin de lnea) en las reglas
se utilizan slo para separar smbolos. Se pueden aadir tantos como se quiera. El signo ':'
separa las dos partes de la regla y el ';' indica el fin de esta. Si existe ms de una regla para
describir a un no terminal, se puede utilizar la siguiente notacin:
parte_izquierda : componentes de la regla 1
| componentes de la regla 2
| ...;
Aunque vayan expresadas de esta forma, Yacc internamente las trata como reglas
independientes. No es necesario poner una regla en cada lnea, pero normalmente se
utiliza esta notacin para hacer la gramtica ms legible.
Si en la parte derecha de una regla no hay nada, significa que el no terminal que
aparece en su parte izquierda acepta la cadena vaca. Por ejemplo, para definir una
secuencia de cero o ms expresiones separadas por comas, se podra escribir:
exps : /* vaco */
| exps1
;
exps1 : exp
| exps1 ',' exp
;
Cuando la parte derecha de una regla est vaca suele escribirse el comentario
/*vaco*/ para hacer ms legible la descripcin de la gramtica.
Acompaando a las reglas pueden aparecer acciones, que determinan su semntica.
Normalmente slo existe una accin para cada regla y va al final de ella, detrs de todos
sus componentes y antes del ';'.
Se pueden poner comentarios en cualquier parte de la decripcin de la gramtica.
Para que una gramtica sea correcta hay que tener en cuenta dos cosas: todos los
smbolos que aparecen en la parte izquierda de alguna regla tienen que ser no terminales
Anlisis Sintctico en Procesadores de Lenguaje
83
y todos los no terminales que aparecen en la descripcin de la gramtica deben estar en la
parte izquierda de al menos una regla de produccin.
Subrutinas
Todo lo incluido en esta seccin se copia literalmente al final del fichero de salida de
Yacc. Este es el lugar ms apropiado para poner cualquier cosa que queramos que
acompae al analizador y que no tenga que estar delante de la definicin de yylex. Los
cdigos de yylex e yyerror suelen estar aqu, aunque tambin pueden ir en ficheros
independientes.
El fichero de salida contiene muchas variables estticas, macros y funciones cuyos
nombres comienzan por yy o YY, por lo que sera recomendable evitar que los nombres
de las variables, funciones, etc. que aparezcan en esta seccin empiecen por estas letras.
En programas pequeos, suele incluirse todo el cdigo que acompaa a yyparse en esta
seccin, pero para proyectos ms complejos es mejor distribuir el texto fuente en varios
ficheros para facilitar la localizacin y correccin de los errores.
Si esta seccin est vaca, se puede omitir el %% que la separa de las reglas
gramaticales.
6.8.5.1.4 Semtica
Las reglas de produccin solamente determinan la sintaxis, es decir, la estructura de las
sentencias del lenguaje. La semntica viene determinada por los valores semnticos
asociados a los tokens y no terminales de la gramtica y por las acciones que aparecen en
las reglas.
Valores semnticos
Una gramtica formal define qu tokens pueden aparecer en cada sitio, por ejemplo, si
una regla menciona el token constante_entera, quiere decir que cualquier constante entera
es sintcticamente vlida en esa posicin, independientemente de su valor. Pero este valor
es muy importante a la hora de ejecutar la entrada, una vez que sta ha sido analizada. Por
esta razn cada token de la gramtica lleva asociado un valor semntico, que puede ser su
valor si representa a un nmero, su nombre si representa a un identificador, etc. Los
signos de puntuacin y operadores aritmticos no tienen valor semntico.
Los smbolos no terminales pueden llevar tambin un valor semntico asociado.
Las reglas gramaticales no utilizan los valores semnticos para nada, stos son
utilizados en las acciones que las acompaan.
En un programa simple, puede ser suficiente con un solo tipo de datos para los valores
semnticos de todos los smbolos de la gramtica. Pero en la mayora de los programas se
necesitarn diferentes tipos de datos, por ejemplo, una constante numrica puede ser de
tipo int o long, mientras que una cadena de caracteres necesita el tipo (char *) y el valor
semntico de un identificador podra ser un puntero a una entrada en la tabla de
smbolos. Para poder utilizar valores semnticos de varios tipos son necesarias dos cosas:
declarar todos los tipos posibles utilizando la clusula %union y elegir uno de esos tipos
Anlisis Sintctico en Procesadores de Lenguaje
84
para cada smbolo que vaya a utilizar valores semnticos, asignndoselos en las
declaraciones %token y %type de la forma vista.
Acciones
Para que sea til, nuestro compilador adems de analizar la entrada, deber producir
una salida. Conseguir esto es la principal funcin de las acciones que acompaan a las
reglas gramaticales.
Las acciones son trozos de programa escritos en C /C++ que van asociados a algunas
reglas gramaticales. Cada vez que el analizador semntico efecte una reduccin
utilizando una de las reglas gramaticales, inmediatamente despus ejecutar la accin
correspondiente a esa regla. La mayor parte de las veces, el propsito de una accin es
hallar el valor semntico del smbolo que aparece en la parte izquierda de la regla a partir
de los valores semnticos de los componentes de su parte derecha. Por ejemplo, la
siguiente regla:
expr : expr '+' expr { $$ = $1 + $3; }
indica que se deben combinar las dos expresiones que aparecen en la parte derecha
separadas por el signo ms, para dar lugar a otra expresin de orden superior. La accin
indica que el valor semntico de la nueva expresin debe ser calculado sumando los
valores semnticos de las otras dos expresiones.
El cdigo de una accin puede hacer referencia a los valores semnticos de los
componentes de la regla, utilizando la construccin $N (siendo N un nmero entero) para
representar el valor del smbolo que aparece en la posicin N de la parte derecha de la
regla y $$ para referirse al valor semntico del smbolo de la parte izquierda de la regla.
Por ejemplo en:
expr1 : expr2 '+' expr3 { $$ = $1 + $3; }
$$ se refiere al valor semntico de expr1, $1 al de expr2 y $3 al de expr3.
Si la accin no asigna un valor semntico a $$, el valor del smbolo que est en la parte
izquierda de la regla ser impredecible y podra ocasionar problemas si se utilizara
posteriormente en otras acciones.
Estas construcciones sern transformadas en referencias a elementos de un array al
pasar al fichero de salida (yyparse).
En $N, N puede ser 0 o negativo, para referenciar smbolos que hayan sido
introducidos en la pila del analizador antes que los de la regla que est siendo analizada.
No es recomendable hacer esto, a menos que se conozca con certeza el contexto en que es
aplicada la regla. Un ejemplo de utilizacin de esta construccin podra ser:
expr1: expr previa '+' expr {...}
| expr previa '-' expr {...}
;
previa: /* vaco */ { expr_previa = $0; }
;
En este caso, $0 se refiere siempre al valor de la expresin que va antes de previa.
Anlisis Sintctico en Procesadores de Lenguaje
85
Si se ha elegido un solo tipo de datos para los valores semnticos, las construcciones
$$ y $N tendrn siempre ese tipo. Pero si se han especificado varios mediante la clusula
%union, entonces se debe indicar para cada smbolo que vaya a tener un valor semntico,
cual va a ser su tipo. Cada vez que se use $$ o $N, el tipo de la construccin vendr
determinado por el del smbolo al que se refiere.
Tambin se puede especificar el tipo en las construcciones $$ o $N, insertando el
nombre del tipo entre $ y lo que venga detrs ($ o N) de la forma:
$<tipo>N o $<tipo>$
Por ejemplo, si tenemos:
%union
{
int tipoi;
double tipod;
}
$<tipoi>1 indicar que el primer smbolo que aparece en la parte derecha de la regla
es de tipo int y de la misma forma $<tipod>1 indicar que es de tipo double.
Los smbolos (tanto terminales como no terminales) pueden ser tratados como
variables ordinarias dentro de las acciones, pueden aparecer en expresiones como
operandos, ser pasados como argumentos a subrutinas, se les pueden asignar valores, etc.
La nica diferencia es que deben ser accedidos utilizando las notaciones $$ y $N. Como
los valores semnticos de los smbolos tienen tipos asignados, el programador debe
ocuparse de que en estas manipulaciones, las acciones se ajusten a las reglas de la
programacin del lenguaje.
Yacc no mira lo que hay dentro de las acciones, por tanto no podr detectar errores de
programacin dentro de ellas.
Si no se especifica ninguna accin para una regla, por defecto se le adjudica {$$ =$1;},
es decir, se hace que el smbolo de la parte izquierda de la regla tome el valor semntico
del primer componente de la parte derecha de la misma.
Adems de las construcciones vistas, en las acciones puede aparecer:
YYERROR. Causa un inmediato error de sintaxis. Esto hace que se llame a
yyerror y que se activen los mecanismos de recuperacin de errores.
YYACCEPT. Hace que yyparse devuelva el control inmediatamente indicando
que el anlisis ha sido vlido.
YYABORT. Hace que yyparse devuelva el control inmediatamente indicando que
se ha producido un error.
yyclearin. Hace que se deseche el lookahead actual. Se utiliza para la recuperacin
de errores.
yyerrok. Hace que se vuelvan a generar mensajes de error.
Anlisis Sintctico en Procesadores de Lenguaje
86
Acciones en mitad de las reglas gramaticales
A veces puede ser til o necesario escribir una accin entre los smbolos de una regla,
es decir, antes del fin de sta. Estas acciones son exactamente iguales que las
convencionales; la nica diferencia es que se ejecutan antes de que el analizador
reconozca la regla completa y por tanto, antes de que se realice la reduccin.
Una accin de este tipo puede hacer referencia a los smbolos que la preceden en la
parte derecha de la regla de la forma habitual, utilizando las construcciones $N, pero no a
los que la siguen, ya que es ejecutada antes de que stos sean analizados. La accin se
cuenta como un componente ms de la regla, de tal forma que si aparece otra ms
adelante en esa misma regla, las construcciones $N de sta debern tenerla en cuenta
como un smbolo ms.
Incluso podrn referirse a su valor semntico (el valor que toma $$ dentro de ella una
vez que es ejecutada) utilizando $N (N ser el valor que le corresponda segn el lugar
que ocupe dentro de la regla). Como su valor semntico no va asociado a ningn smbolo
gramatical, no se puede declarar su tipo, por lo que habr que utilizar $<...>N cada vez
que este valor sea utilizado en acciones posteriores.
Un ejemplo muy simple de utilizacin de este tipo de acciones podra ser:
exp : factor '*' factor { $<tipoi>$ = $1 * $3 ; }
'+' sumando { $$ = $<tipoi>4 + $6 ; }
;
La utilizacin de acciones en mitad de las reglas gramaticales puede ocasionar
problemas en el anlisis, por lo que es recomendable evitarlas.
Cualquier accin de ese tipo puede pasar a ser una accin normal (al final de la regla).
Considerando el ejemplo anterior podemos hacer:
producto : factor '*' factor { $$ = $1 * $3 ; } ;
exp : producto '+' sumando { $$ = $1 + $3 ; } ;
con lo que habremos evitado la accin en mitad de la regla.
Yacc, cada vez que se encuentra una de estas acciones, genera un no terminal falso que
pasa a sustituir a la accin en mitad de la regla y aade una regla a la gramtica, que
tendr como parte izquierda el no terminal generado y la parte derecha vaca y con esa
accin al final. Para el ejemplovisto sera:
@1 : /* vaco */ { $<tipoi>$ = $1 * $3 ; } ;
exp : factor '*' factor @1
'+' sumando { $$ = $<tipoi>4 + $6 ; } ;
El nombre del no terminal generado ser siempre @numero (numero se incrementa
cada vez que se produce uno de estos smbolos, para que no pueda haber dos con el
mismo nombre).
Anlisis Sintctico en Procesadores de Lenguaje
87
6.8.5.1.5 Recursividad
Cuando en una regla de produccin el smbolo de la parte izquierda aparece tambin
entre los componentes de la parte derecha, se dice que la regla es recursiva. Casi todas las
gramticas libres de contexto utilizan la recursividad para definir una secuencia de 0 o
ms apariciones de algo. Por ejemplo:
exprs : expr
| exprs ',' expr
;
define una secuencia de una o ms expresiones separadas por comas. Al menos una de
las reglas debe llevar fuera de la recursin, si no se entrara en un bucle infinito.
Como exprs aparece al principio de la parte derecha de la regla, se dice que la regla es
recursiva a la izquierda. Por el contrario:
exprs : expr
| expr ',' exprs
;
ser recursiva a la derecha. Los dos tipos de recursividad hacen lo mismo pero de
distinta forma.
Cualquier tipo de secuencia puede ser definida utilizando recursividad a la izquierda o
a la derecha. En los analizadores LR (y en todos los ascendentes en general) se debe
utilizar siempre recursividad a la izquierda porque ocupa menos espacio en la pila del
analizador. Por ejemplo dadas las siguientes reglas:
suma : num [1]
| suma '+' num [2]
;
(suponemos num : 0 | 1 | .. | 9 ; )
si queremos reconocer la expresin 9 + 4 + 5, los contenidos de la pila en las sucesivas
etapas del anlisis sern:
num(9) suma(9) suma(9)
+
suma(9)
+
num(4)
suma(13) suma(13)
+
suma(13)
+
num(5)
suma(18)
8 7 6 5 4 3 2 1

1. Se carga num (9) en la pila.
2. Se reduce num (9) a suma (9) utilizando la regla [1].
3. Se carga '+' en la pila.
Anlisis Sintctico en Procesadores de Lenguaje
88
4. Se carga num (4) en la pila.
5. Se reduce num (4) + suma (9) a suma (13) utilizando la regla [2].
6. Se carga '+' en la pila
7. Se carga num (5) en la pila.
8. Se reduce num (5) + suma (13) a suma (18) utilizando la regla [2].
Los nmeros entre parntesis indican el valor semntico de los smbolos que aparecen
delante de ellos.
La pila nunca llega a contener ms de tres elementos. En cambio, si la regla fuera:
suma : num [1]
| num '+' suma [2]
;
los contenidos de la pila seran:

num(9) num(9)
+
num(9)
+
num(4)
num(9)
+
suma(9)
suma(18) num(9)
+
num(4)
+
num(5)
num(9)
+
num(4)
+
num(9)
+
num(4)
+
suma(5)
1 2 3 4 5 6 7 8

1. Se carga num (9) en la pila
2. Aqu aparece un conflicto shift/reduce. El analizador tiene que elegir entre cargar
el smbolo siguiente ('+') o reducir num a suma utilizando la regla [1]. Si no se le
indica otra cosa, elige siempre cargar. Pero aunque quisiramos no podramos
hacer la reduccin porque no tenemos ninguna regla cuya parte derecha empiece
con suma as que nunca llegaramos a reconocer la cadena completa por este
camino. Por lo tanto se carga '+' en la pila.
3. Se carga num (4) en la pila.
4. Se carga '+' en la pila (aqu ocurre lo mismo que en el apartado 2).
5. Se carga num (5) en la pila.
6. Esta vez ya no nos queda nada que cargar, as que habr que reducir num (5) a
suma (5) utilizando la regla [1].
7. Se reduce num (4) + suma (5) a suma (9) utilizando la regla [2].
8. Se reduce num (9) + suma (9) a suma (18) utilizando la regla [2].
Anlisis Sintctico en Procesadores de Lenguaje
89
9. En esta caso la pila llega a tener hasta cinco elementos.
En este ejemplo simplsimo, no tiene mucha importancia la diferencia entre una y otra
recursividad, pero en situaciones ms complicadas, el utilizar la recursividad a la derecha
podra incluso llegar a agotar la capacidad de la pila del analizador.
Por razones similares, en analizadores descendentes, en los que se utilizan
derivaciones en lugar de reducciones, se debe evitar la recursividad a la izquierda,
utilizando siempre recursividad a la derecha.
Hasta ahora slo hemos visto recursividad directa. Pero tambin existe la recursividad
indirecta, que es la que se produce cuando el smbolo de la parte izquierda no aparece en
la parte derecha de su misma regla, pero s en las partes derechas de otras reglas que
definen smbolos no terminales que s aparecen en la parte derecha de su regla. Por
ejemplo:
exp: termino
| termino '+' termino
;
termino : constante
| '(' exp ')'
;
en la parte derecha de la definicin de exp aparece termino y en la parte derecha de la
definicin de termino aparece exp, por tanto este ser un caso de recursividad indirecta.
6.8.5.1.6 Conflictos, ambigedad y precedencia
A la hora de elaborar un analizador sintctico, siempre es mejor trabajar con
gramticas no ambiguas, porque si es posible reconocer una sentencia mediante distintas
secuencias de reglas gramaticales (es decir, produciendo varios rboles sintcticos
distintos), aunque desde el punto de vista sintctico todos estos rboles producen el
mismo resultado, semnticamente no es as. Se producirn diferentes resultados
semnticos dependiendo de que acciones y en qu orden se ejecuten. Por ejemplo si
tenemos la gramtica:
%token NUMERO
%%
expr : expr '+' expr { $$ = $1 + $3; } [1]
| expr '*' expr { $$ = $1 * $3; } [2]
| NUMERO { $$ = $1; } [3]
;
dada la expresin 3 + 2 * 5, existen al menos dos rboles sintcticos que la reconocen:
NUMERO(3) + NUMERO(2) * NUMERO(5) (aplicando [3] se pasa a:)
expr(3) + NUMERO(2) * NUMERO(5) (aplicando [3]:)
expr(3) + expr(2) * NUMERO(5) (aplicando [1]:)
expr(5) * NUMERO(5) (aplicando [3]:)
expr(5) * expr(5) (aplicando [2]:)
Anlisis Sintctico en Procesadores de Lenguaje
90
expr(25)
y
NUMERO(3) + NUMERO(2) * NUMERO(5) (aplicando [3]:)
expr(3) + NUMERO(2) * NUMERO(5) (aplicando [3]:)
expr(3) + expr(2) * NUMERO(5) (aplicando [3]:)
expr(3) + expr(2) * expr(5) (aplicando [2]:)
expr(3) + expr(10) (aplicando [1]:)
expr(13)
En ambos casos se reconoce la expresin como sintcticamente correcta, pero en el
primer caso se produce 25 como resultado y en el segundo 13, por lo tanto slo es vlido
el segundo rbol.
En este ejemplo es fcil eliminar la ambigedad, bastar con asignarles la precedencia
adecuada al producto y a la suma. Pero no siempre es as de sencillo, incluso hay casos
en los que no se la puede eliminar del todo.
El tratar de analizar sintcticamente sentencias de una lenguaje generado por una
gramtica ambigua puede producir problemas. En los analizadores LR, y en concreto en
yyparse, la ambigedad de las gramticas da lugar a lo que se conoce como conflictos
shift/reduce y conflictos reduce/reduce.
Conflictos shift/reduce (s/r)
Para analizar la entrada, el analizador sintctico va cargando en la pila los tokens que
le pasa el analizador lxico y cuando los elementos que ocupan su parte superior se
ajustan a alguna de las reglas de la gramtica realiza una reduccin, sustituyendo estos
smbolos por el que aparece en la parte izquierda de la regla aplicada.
Cuando en un momento determinado del anlisis, el analizador no sabe si debe cargar
(shift) o reducir (reduce), se dice que se ha producido un conflicto s/r.
Por ejemplo, un caso tpico es el de las sentencias if-then e if-then-else. Si tenemos las
reglas:
sentencia_if : IF expr THEN sentencias
| IF expr THEN sentencias ELSE sentencias
;
cuando se lee el token ELSE, el analizador puede hacer dos cosas: reducir utilizando la
primera regla, o cargar el ELSE para reducir ms adelante utilizando la segunda regla.
Entonces se ha producido un conflicto s/r.
Cuando aparece uno de estos conflictos, el analizador elige siempre cargar (a menos
que se le indique otra cosa), porque la mayor parte de las veces sta es la opcin ms
adecuada. En el ejemplo visto, cada ELSE que aparezca en la entrada ser relacionado
con el IF ya ledo ms cercano a l (que ser el ms interno si hay varios anidados) y esto
es lo que hacen la mayora de los lenguajes de programacin que utilizan esta sentencia.
Por supuesto sera mejor que la gramtica no fuera ambigua, pero eso en este caso es
bastante difcil de conseguir.
Anlisis Sintctico en Procesadores de Lenguaje
91
Pero no siempre que se produce un conflicto s/r se debe cargar. Por ejemplo, si
estamos analizando la expresin 3*5+1, al llegar al signo '+' se produce un conflicto s/r.
Si esta vez eligiramos cargar iramos en contra de las leyes de la aritmtica, que dicen
que el producto tiene una precedencia superior a la de la suma.
As pues, en las expresiones aritmticas, para decidir si se debe cargar o reducir habr
que tener en cuenta las precedencias y asociatividades de los operadores, que se definen
en la seccin de declaraciones de la forma vista.
Conflictos reduce/reduce (r/r)
Se produce un conflicto r/r cuando en un momento determinado del anlisis se puede
reducir utilizando dos o ms reglas distintas.
Por ejemplo, si tenemos:
%token NUMERO
%token IDENTIFICADOR
%token GOTO
%%
programa : sentencia [1]
| programa sentencia [2]
;
sentencia : sent_goto [3]
| expresion [4]
;
sent_goto : GOTO etiqueta [5]
;
etiqueta : IDENTIFICADOR [6]
;
expresion : expresion '+' expresion [7]
| NUMERO [8]
| IDENTIFICADOR [9]
;
El analizador, cuando se encuentra un IDENTIFICADOR, tiene que elegir entre
reducir a expresion utilizando la regla [9] o a etiqueta mediante la [6].
Por defecto, Yacc elige reducir utilizando la regla que aparece primero en la
descripcin de la gramtica, pero esto es muy arriesgado.
Generalmente, los conflictos r/r se producen por errores en el diseo del lenguaje o en
la escritura de la gramtica. Si el error se produjo en el diseo del lenguaje, habr que
revisarlo y disearlo de nuevo. Si el conflicto se produjo por una mala escritura de la
gramtica, existen algunos mtodos para eliminarlo. Uno de ellos consiste en utilizar
directamente las partes derechas de las reglas que produjeron el conflicto en todos los
sitios donde apareca la parte izquierda de stas. Siguiendo con el ejemplo anterior, las
Anlisis Sintctico en Procesadores de Lenguaje
92
reglas que producan el conflicto eran la [6] y la [9]. Si suprimimos la regla [6] y
reescribimos la [5] como sigue:
sent_goto : GOTO IDENTIFICADOR
;
habremos eliminado el conflicto r/r.
Pero no siempre es tan fcil hacerlo, no existe un mtodo general que elimine los
conflictos r/r de las gramticas. Cada conflicto deber ser estudiado detenidamente y, a
ser posible, eliminado.
Precedencia
No slo los smbolos terminales tienen precedencia, tambin las reglas de produccin
pueden tenerla. Cada regla toma la precedencia y asociatividad del ltimo smbolo
terminal especificado en su parte derecha (si existe este smbolo y si tiene precedencia y
asociatividad declaradas) a menos que se indique explcitamente otra cosa. Para indicar
explcitamente la precedencia y asociatividad de una regla se utiliza la declaracin %prec,
que es especialmente til cuando un mismo terminal tiene varios significados
dependiendo del contexto en que aparezca. Por ejemplo, el signo menos puede ser tilizado
como operador binario para realizar restas y como operador unario para el cambio de
signo. La precedencia es distinta segn se trate de un caso o de otro.
Las declaraciones deprecedencia y asociatividad (%left, %right y %nonassoc) slo
pueden ser usadas una vez para cada token, por lo tanto un token slo tiene una
precedencia declarada de esta forma. Para declarar la otra habr que utilizar %prec. Esta
declaracin indica cual es la precedencia de una regla particular especificando un smbolo
terminal cuya precedencia sea la misma que queremos que tenga esa regla. No es
necesario que ese terminal aparezca en otra parte de la regla. La sintaxis de esta
declaracin es :
%prec TERMINAL
y se escribe despus de los componentes de la parte derecha de la regla y antes de la
accin (si la hay). Lo que hace es asignar a la regla la precedencia de TERMINAL en
lugar de la que tendra dependiendo del ltimo smbolo terminal que apareciera en su
parte derecha.
La definicin de una gramtica que describa expresiones aritmticas incluyendo los
dos usos de '-' puede ser:
%token NUMERO
%left '+' '-'
%left '*' '/'
%left MENOSUNARIO
%%
expr : expr '+' expr { $$ = $1 + $3; }
| expr '-' expr { $$ = $1 - $3; }
| expr '*' expr { $$ = $1 * $3; }
| expr '/' expr { $$ = $1 / $3; }
Anlisis Sintctico en Procesadores de Lenguaje
93
| '-' expr %prec MENOSUNARIO { $$ = - $2; }
| NUMERO
;
El smbolo terminal MENOSUNARIO no se utiliza para nada en la descripcin de la
gramtica, su nico fin es representar la precedencia ms alta para poder asignrsela a la
regla que maneja el menos unario.
Reglas que aplica Yacc para resolver conflictos
1. En un conflicto s/r por defecto se elige cargar.
2. En un conflicto r/r por defecto se reduce aplicando la regla gramatical anterior en
la secuencia de entrada.
La precedencia y asociatividad se registra para los tokens que los tienen.
La precedencia y asociatividad asociada a una regla gramatical, es la precedencia y
asociatividad del ltimo token de la regla. Algunas reglas gramaticales pueden no tener
precedencias o asociatividad relacionadas con ellas.
Si se produce un conflicto s /r, lo que se hace es comparar la precedencia de la regla que
est siendo considerada con la del token que va a ser ledo a continuacin.
Si es mayor la del token se elige cargar y si es mayor la de la regla se
reduce.
Si tienen la misma precedencia, la eleccin se basa en la asociatividad: si
en ese nivel de precedencia, el token es asociativo de izquierda a derecha
(%left) se elige reducir, si lo es de derecha a izquierda (%right) se carga y si
no es asociativo se producir un error.
Si la regla o el token que se va a leer no tienen precedencia, por defecto se
elige cargar.

6.8.5.1.7 Funciones que acompaan a yyparse
Yacc no produce un programa C completo sino una funcin que no podr ser ejecutada
si no se le aaden algunas ms. Bsicamente habr que aadir una funcin principal
(main), una funcin analizador lxico (yylex) y una funcin de manejo de errores
(yyerror). Normalmente se aaden ms, pero slo estas tres son imprescindibles.
La funcin principal main
Esta funcin, en su forma ms simple consistir en una llamada a yyparse:
main()
{
yyparse;
}
Anlisis Sintctico en Procesadores de Lenguaje
94
pero esta simplsima main slo podr ser utilizada en programas tan simples como ella.
Generalmente, adems de llamar a yyparse se ocupar de inicializar, abrir ficheros,
procesar los argumentos de la lnea de comandos, etc. antes de la llamada a yyparse, y de
cerrar ficheros, comunicar si el anlisis ha resultado correcto o no, etc.
Se puede llamar a yyparse directamente desde main o bien desde alguna otra funcin,
que a su vez sea llamada por main.
La funcin analizador lxico yylex
Dentro de un compilador, el analizador sintctico se encarga de agrupar tokens en
sentencias, pero no sabe tratar el fichero de entrada para obtener de l los tokens, de eso
se encarga el analizador lxico. Lo que Yacc genera es un analizador sintctico (yyparse),
que, como tal, no sabe leer un fichero y descomponerlo en tokens, as que necesita un
analizador lxico que lo haga por l. yyparse supone que ste existe y que es una funcin
de nombre yylex a la que llama cada vez que necesita un nuevo token, por lo tanto el
programador debe suministrar una funcin con ese nombre que le pase los tokens del
fichero de entrada a yyparse.
Cada vez que yyparse la llama, yylex le devuelve el cdigo del token que acaba de leer
o 0 si lleg al final del fichero de entrada. A todos los tokens definidos en la gramtica se
les asigna un cdigo numrico mediante una #define, que aparecer al principio del
fichero producido por Yacc, as que yylex puede referirse a ellos utilizando su nombre en
lugar del nmero que les corresponde. Los literales de un solo carcter, aunque no tengan
la #define correspondiente, pueden ser usados de la misma forma, ya que el nmero que
Yacc les asigna es el que les corresponde segn el cdigo ASCII, excepto el carcter nulo,
ya que su cdigo numrico es 0 y ese es el valor que se utiliza para indicar el final de la
entrada (Yacc tambin toma como fin de fichero cualquier valor negativo).
yylex al devolver el control a yyparse, le dice cul es el token que acaba de leer, pero
no qu valor semntico tiene. Por ejemplo, si tenemos una gramtica que reconoce
expresiones aritmticas, la sentencia de tres tokens: 31 + 13 har que yylex le pase a
yyparse la secuencia NUMERO '+' NUMERO. Esta es toda la informacin que el
analizador sintctico necesita para saber si la sentencia es sintcticamente correcta o no.
Los valores semnticos se utilizan en la ejecucin de las acciones, pero yyparse no se
preocupa de averiguarlos ni de ver si son correctos o no. Supone que el del ltimo token
ledo est almacenado en la variable yylval. yylex debe encargarse de leerlo y depositarlo
en esa variable.
En resumen, lo que tiene que hacer yylex cada vez que yyparse lo llama es:
Leer el token que viene a continuacin en el fichero de entrada.
Ver qu clase de token es.
Leer su valor semntico (si lo tiene) y almacenarlo en yylval.
Devolver el token ledo.
Si el analizador lxico no va includo en la seccin de cdigo adicional, no estar en el
fichero de salida de Yacc y por lo tanto no podr utilizar las #define generadas por los
tokens, pero si al ejecutar Yacc se especifica la opcin '-d', stas, adems de ir en el de
Anlisis Sintctico en Procesadores de Lenguaje
95
yyparse, sern escritas en un fichero de cabecera, que podr ser incluido en el fichero de
yylex.
Se puede tomar la salida de la utilidad LEX (generador de analizadores lxicos
incorporado al sistema operativo UNIX) o de programas similares, compatibles con ella,
como analizador lxico para yyparse sin necesidad de hacer ningn cambio en ella.
La funcin yyerror
La principal funcin de esta rutina es informar al programador de que se ha producido
un error, imprimiendo un mensaje, que yyparse le pasar como argumento al llamarla (la
llamar cada vez que encuentre un error). En su forma ms simple ser:
void yyerror(s)
char *s;
{
printf("%s\n", s);
}
aunque generalmente ser algo ms complicada.
6.8.5.1.8 Recuperacin de errores
Cuando un compilador analiza un programa fuente, no debe finalizar su ejecucin cada
vez que se encuentra un error. Lo ideal sera que informara de todos los errores
contenidos en l en una sola compilacin.
Normalmente cuando encuentra un error, yyparse llama a la rutina yyerror y finaliza su
ejecucin. Pero existen algunos mecanismos que pueden hacer que el analizador se
recupere de los errores y sea capaz de seguir adelante con el proceso.
En un analizador interactivo simple en el que cada entrada sea una lnea, puede ser
suficiente con hacer que yyparse devuelva 1 si se produce un error, ignorar el resto de la
lnea de entrada y despus llamar a yyparse de nuevo para que analice la siguiente. Pero
para un compilador esto no es la solucin ms adecuada porque se pierde el contexto.
Para permitir al usuario algn control sobre este proceso, yacc proporciona un
dispositivo simple, pero razonablemente general que ayuda a recuperarse de los errores,
utilizando para ello el token especial error y las macros yyclearin e yyerrok.
El token error es un smbolo terminal especial reservado para el manejo de errores. No
es necesario declararlo, Yacc lo hace automticamente. Para explicar su utilizacin
consideremos:
sent : /* vaco */ [1]
| sent '\n' [2]
| sent exp '\n' [3]
| sent error '\n' [4]
;
La regla [4] dice que puede aparecer un error seguido de una sentencia (sent) y antes
de un carcter nueva lnea. De esta forma, si se produce un error en medio de una
Anlisis Sintctico en Procesadores de Lenguaje
96
expresin (exp), se puede hacer una reduccin al token error y despus reducir utilizando
la regla [4]. Estas reducciones no son exactamente iguales a las que se producen
normalmente en el analizador, hay que hacer que la situacin se ajuste a la regla. En
primer lugar habr que eliminar estados y smbolos de la pila, retrocediendo hasta un
lugar en el que el token error sea aceptable cargar este token en la pila. Despus se salta
todo lo que aparece en la entrada hasta llegar a algo que se ajuste a lo que viene tras error
en la regla ('\n', en este caso), que ser cargado en la pila para posteriormente reducir por
la regla que contiene error.
Es aconsejable poner algn token detrs de error en la regla, para que el analizador
pueda saber exactamente qu parte de la entrada tiene que saltar para volver a analizar
normalmente. Tambin es til, cuando se produce un error, tener en cuenta las parejas de
parntesis, llaves, corchetes, etc., porque podra suceder que el de apertura fuera
reconocido antes de producirse el error y el de cierre no, lo que podra producir un
mensaje de un error que no existe en realidad. Si en la gramtica aparece:
sent : '(' exp ')'
| '(' error ')'
;
si se produjera un error dentro de un parntesis, se saltara todo el texto de entrada que
viniera a continuacin hasta llegar al prximo ')'.
Estas estrategias de recuperacin de errores se basan en suposiciones, el programador
las utilizar en aquellos sitios en que "supone" que se pueden producir errores. Si luego
las cosas no sucedieran de esta forma, un error podra llevar a otro. En estos casos, para
evitar que se produzcan demasiados mensajes de error, que podran confundir ms que
aclarar las cosas, el analizador no produce ningn mensaje ms hasta que haya
conseguido cargar con xito tres tokens seguidos.
La macro yyerrok informa al analizador de que el error ha sido completamente
superado y hace que vuelvan a salir mensajes de error, aunque an no se hayan cargado
tres tokens despus del ltimo que se produjo. Tanto sta como yyclearin son sentencias
C vlidas que pueden ser utilizadas en las acciones de las reglas de produccin. Por
ejemplo, en el fichero de entrada a Yacc podra aparecer:
sent : '(' exp ')'
| '(' error ')' { yyerrok; }
;
Despus de la recuperacin de un error, el lookahead que tenamos en el momento en
que ste se produjo es reanalizado. Si ya no es vlido, se puede utilizar la macro
yyclearin, que se ocupa de que sea desechado.
6.8.5.1.9 Funcionamiento de yyparse
La funcin de yyparse es ir agrupando los tokens que le pasa el analizador lxico yylex
de acuerdo con las reglas de produccin de la gramtica y ejecutar las acciones
correspondientes a las reglas que utiliza. Si la entrada es vlida, la secuencia de tokens
completa se reduce a un solo smbolo (que ser el smbolo inicial de la gramtica).
Finaliza su ejecucin cuando encuentre el final de la entrada o un error sintctico del que
no se pueda recuperar.
Anlisis Sintctico en Procesadores de Lenguaje
97
Devuelve el valor 0 si no hubo problemas en el anlisisy 1 si se produjo algn error.
Dentro de las acciones que acompaan a las reglas gramaticales se pueden utilizar dos
macros que hacen que el anlisis finalice inmediatamente. Son:
YYACCEPT. Devuelve el control y el valor 0 (se ha completado el anlisis
con xito).
YYABORT. Devuelve el control y el valor 1 (se ha producido un error).
El valor semntico del ltimo token estar almacenado en la variable yylval (la funcin
yylex debe encargarse de dejarlo all). Esta es una variable de tipo YYSTYPE, que es el
que utilizan los elementos de la pila de yyparse.
Si todos los valores semnticos de los smbolos de la gramtica son del mismo tipo,
yylval tambin lo ser, pero si se utilizan mltiples tipos de datos, el tipo de yylval ser
una unin construida a partir de la declaracin %union.En este caso, al almacenar el valor
de un token deber usarse el campo adecuado de la unin.
yyparse va cargando en la pila los tokens que le pasa yylex, hasta llegar a una situacin
en la que se pueda producir una reduccin. Entonces consulta el token lookahead y las
tablas de anlisis para saber si tiene que reducir y si es as realiza la reduccin
correspondiente y despus ejecuta la accin semntica que lleva asociada la regla por la
que se redujo.
6.8.5.1.10 Ficheros generados por yacc
Yacc puede generar hasta tres ficheros: el de salida, que contiene la funcin yyparse, el
que contiene informacin acerca del funcionamiento del analizador generado (fichero de
conflictos y estados) que se gernera al utilizar la opcin -v al ejecutar Yacc, y el de
cabecera, que contiene las #define de los tokens y que se produce si se utiliza la opcin -d
al ejecutar Yacc. A continuacin estudiaremos cada uno de ellos.
El fichero de salida
El fichero que se produce como salida de Yacc contiene un analizador LR, que como
tal consta de las siguientes partes:
Programa analizador. Es la estructura que se utiliza para construir el fichero
de salida y ser el contenido del fichero SIMPLE.PRS a menos que se utilice
la opcin -p indicando el nombre del fichero que contiene la estructura que se
quiere utilizar. En l se insertan las acciones de las reglas de la gramtica en
donde aparece el smbolo $.
Tabla de anlisis. En cierto modo se podra decir que la tabla de anlisis es
yytabla, que consta de dos partes, una a la que se accede por medio de yypacc
(accion) y otra a la que se llega mediante yypgoto (goto). Pero en realidad,
todas las tablas que aparecen al principio del fichero de salida forman parte de
la tabla de anlisis, ya que todas ellas se utilizan de una forma u otra para
acceder a yytabla.
Pila de analizador. Todo analizador LR, utiliza una pila en donde va
almacenando los smbolos de la entrada que lee y los estados por los que pasa.
La pila de yyparse consta en realidad de tres "subpilas" paralelas, una de ellas
Anlisis Sintctico en Procesadores de Lenguaje
98
de tipo YYSTYPE (definido en la seccin de declaraciones) almacena los
smbolos de la gramtica (es yyvsa), otra, de tipo short, almacena los nmeros
de los estados por los que va pasando el analizador (yyssa) y la otra almacena
por cada smbolo la informacin necesaria para localizarlo con las
construcciones "@N" (yylsa).
La entrada ser lo que yylex le vaya pasando y la salida el anlisis sintctico de la
entrada.
Al final del analizador generado se incluye todo lo que apareca en la seccin de
cdigo adicional en el fichero de entrada a Yacc.
El fichero de cabecera
Es el fichero que se produce con la opcin -d o -D. Contiene todas las definiciones de
los tipos y macros que utiliza yyparse adems de las de los tokens de la gramtica.
Por ejemplo, si ejecutramos Pcyacc utilizando la opcin -d con el siguiente fichero de
entrada:
%{
#define YYSTYPE double
#include <math.h>
%}
%token NUM
%left '-' '+'
%left '*' '/'
%left NEG
%right '^'
/* gramtica */
%%
input: /* vacio */
| input linea
;
linea: '\n'
| exp '\n' { printf("\t%.10g\n", $1); }
| error '\n' { yyerrok; }
;
exp: NUM { $$ = $1; }
| exp '+' exp { $$ = $1 + $3; }
| exp '-' exp { $$ = $1 - $3; }
| exp '*' exp { $$ = $1 * $3; }
| exp '/' exp { $$ = $1 / $3; }
| '-' exp %prec NEG { $$ = -$2; }
| exp '^' exp { $$ = pow ($1, $3); }
| '(' exp ')' { $$ = $2; }
Anlisis Sintctico en Procesadores de Lenguaje
99
;
%%
/* cdigo c adicional */
/* analizador lxico */
#include <ctype.h>
yylex ()
{
int c;
while ((c = getchar()) == ' ' || c == '\t') ;
if (c == '.' || isdigit (c))
{
ungetc(c, stdin);
scanf ("%lf", &yylval);
return NUM;
}
if (c == EOF) return 0;
return c;
}
/* principal */
main()
{
yyparse();
}
/* subrutina de error */
#include <stdio.h>
yyerror (s)
char *s;
{
printf ("%s\n", s);
}

obtendramos el fichero de cabecera con la siguiente informacin:

#define NUM 258
#define NEG 259

El fichero de conflictos y estados
Al ejecutar Yacc con la opcin -v o -V se produce un fichero que contiene informacin
acerca de los conflictos producidos y de los estados del analizador generado.
Dependiendo de la herramienta utilizada, la informacin podr variar pero en todas se
Anlisis Sintctico en Procesadores de Lenguaje
100
utiliza una estructura similar a la que se muestra a continuacin. Para el ejemplo visto en
el apartado anterior sera:
CONFLICTOS RESUELTOS:
En el estado, 10 entre la regla 11 y el token '-'. Resuelto como reducir.
En el estado, 10 entre la regla 11 y el token '+'. Resuelto como reducir.
En el estado, 10 entre la regla 11 y el token '*'. Resuelto como reducir.
En el estado, 10 entre la regla 11 y el token '/'. Resuelto como reducir.
En el estado, 10 entre la regla 11 y el token '^'. Resuelto como cargar.
En el estado, 19 entre la regla 8 y el token '-'. Resuelto como reducir.
En el estado, 19 entre la regla 8 y el token '+'. Resuelto como reducir.
En el estado, 19 entre la regla 8 y el token '*'. Resuelto como cargar.
En el estado, 19 entre la regla 8 y el token '/'. Resuelto como cargar.
En el estado, 19 entre la regla 8 y el token '^'. Resuelto como cargar.
En el estado, 20 entre la regla 7 y el token '-'. Resuelto como reducir.
En el estado, 20 entre la regla 7 y el token '+'. Resuelto como reducir.
En el estado, 20 entre la regla 7 y el token '*'. Resuelto como cargar.
En el estado, 20 entre la regla 7 y el token '/'. Resuelto como cargar.
En el estado, 20 entre la regla 7 y el token '^'. Resuelto como cargar.
En el estado, 21 entre la regla 9 y el token '-'. Resuelto como reducir.
En el estado, 21 entre la regla 9 y el token '+'. Resuelto como reducir.
En el estado, 21 entre la regla 9 y el token '*'. Resuelto como reducir.
En el estado, 21 entre la regla 9 y el token '/'. Resuelto como reducir.
En el estado, 21 entre la regla 9 y el token '^'. Resuelto como cargar.
En el estado, 22 entre la regla 10 y el token '-'. Resuelto como reducir.
En el estado, 22 entre la regla 10 y el token '+'. Resuelto como reducir.
En el estado, 22 entre la regla 10 y el token '*'. Resuelto como reducir.
En el estado, 22 entre la regla 10 y el token '/'. Resuelto como reducir.
En el estado, 22 entre la regla 10 y el token '^'. Resuelto como cargar.
En el estado, 23 entre la regla 12 y el token '-'. Resuelto como reducir.
En el estado, 23 entre la regla 12 y el token '+'. Resuelto como reducir.
En el estado, 23 entre la regla 12 y el token '*'. Resuelto como reducir.
En el estado, 23 entre la regla 12 y el token '/'. Resuelto como reducir.
En el estado, 23 entre la regla 12 y el token '^'. Resuelto como cargar.
TIPOS DE TOKENS:
numero -1 -> $
Anlisis Sintctico en Procesadores de Lenguaje
101
numero 10 -> '\n'
numero 40 -> '('
numero 41 -> ')'
numero 42 -> '*'
numero 43 -> '+'
numero 45 -> '-'
numero 47 -> '/'
numero 94 -> '^'
numero 256 -> error
numero 258 -> NUM
numero 259 -> NEG
---------------------------- ESTADO 0 ---------------------------
input cargar y pasar al estado 1
$default reducir por 1 (input)

---------------------------- ESTADO 1 ---------------------------
input -> input . linea (2)
$ cargar y pasar al estado 24
error cargar y pasar al estado 2
NUM cargar y pasar al estado 3
'-' cargar y pasar al estado 4
'\n' cargar y pasar al estado 5
'(' cargar y pasar al estado 6
linea cargar y pasar al estado 7
exp cargar y pasar al estado 8

---------------------------- ESTADO 2 ---------------------------
linea -> error . '\n' (5)
'\n' cargar y pasar al estado 9

---------------------------- ESTADO 3 ---------------------------
exp -> NUM . (6)
$default reducir por 6 (exp)

---------------------------- ESTADO 4 ---------------------------
exp -> '-' . exp (11)
NUM cargar y pasar al estado 3
'-' cargar y pasar al estado 4
'(' cargar y pasar al estado 6
Anlisis Sintctico en Procesadores de Lenguaje
102
exp cargar y pasar al estado 10

---------------------------- ESTADO 5 ---------------------------
linea -> '\n' . (3)
$default reducir por 3 (linea)

---------------------------- ESTADO 6 ---------------------------
exp -> '(' . exp ')' (13)
NUM cargar y pasar al estado 3
'-' cargar y pasar al estado 4
'(' cargar y pasar al estado 6
exp cargar y pasar al estado 11

---------------------------- ESTADO 7 ---------------------------
input -> input linea . (2)
$default reducir por 2 (input)

---------------------------- ESTADO 8 ---------------------------
linea -> exp . '\n' (4)
exp -> exp . '+' exp (7)
exp -> exp . '-' exp (8)
exp -> exp . '*' exp (9)
exp -> exp . '/' exp (10)
exp -> exp . '^' exp (12)
'-' cargar y pasar al estado 12
'+' cargar y pasar al estado 13
'*' cargar y pasar al estado 14
'/' cargar y pasar al estado 15
'^' cargar y pasar al estado 16
'\n' cargar y pasar al estado 17

---------------------------- ESTADO 9 ---------------------------
linea -> error '\n' . (5)
$default reducir por 5 (linea)

---------------------------- ESTADO 10 --------------------------
exp -> exp . '+' exp (7)
exp -> exp . '-' exp (8)
exp -> exp . '*' exp (9)
exp -> exp . '/' exp (10)
exp -> '-' exp . (11)
Anlisis Sintctico en Procesadores de Lenguaje
103
exp -> exp . '^' exp (12)
'^' cargar y pasar al estado 16
$default reducir por 11 (exp)

---------------------------- ESTADO 11 --------------------------
exp -> exp . '+' exp (7)
exp -> exp . '-' exp (8)
exp -> exp . '*' exp (9)
exp -> exp . '/' exp (10)
exp -> exp . '^' exp (12)
exp -> '(' exp . ')' (13)
'-' cargar y pasar al estado 12
'+' cargar y pasar al estado 13
'*' cargar y pasar al estado 14
'/' cargar y pasar al estado 15
'^' cargar y pasar al estado 16
')' cargar y pasar al estado 18
---------------------------- ESTADO 12 --------------------------
exp -> exp '-' . exp (8)
NUM cargar y pasar al estado 3
'-' cargar y pasar al estado 4
'(' cargar y pasar al estado 6
exp cargar y pasar al estado 19

---------------------------- ESTADO 13 --------------------------
exp -> exp '+' . exp (7)
NUM cargar y pasar al estado 3
'-' cargar y pasar al estado 4
'(' cargar y pasar al estado 6
exp cargar y pasar al estado 20

---------------------------- ESTADO 14 --------------------------
exp -> exp '*' . exp (9)
NUM cargar y pasar al estado 3
'-' cargar y pasar al estado 4
'(' cargar y pasar al estado 6
exp cargar y pasar al estado 21

---------------------------- ESTADO 15 --------------------------
exp -> exp '/' . exp (10)
NUM cargar y pasar al estado 3
Anlisis Sintctico en Procesadores de Lenguaje
104
'-' cargar y pasar al estado 4
'(' cargar y pasar al estado 6
exp cargar y pasar al estado 22

---------------------------- ESTADO 16 --------------------------
exp -> exp '^' . exp (12)
NUM cargar y pasar al estado 3
'-' cargar y pasar al estado 4
'(' cargar y pasar al estado 6
exp cargar y pasar al estado 23

---------------------------- ESTADO 17 --------------------------
linea -> exp '\n' . (4)
$default reducir por 4 (linea)

---------------------------- ESTADO 18 --------------------------
exp -> '(' exp ')' . (13)
$default reducir por 13 (exp)

---------------------------- ESTADO 19 --------------------------
exp -> exp . '+' exp (7)
exp -> exp . '-' exp (8)
exp -> exp '-' exp . (8)
exp -> exp . '*' exp (9)
exp -> exp . '/' exp (10)
exp -> exp . '^' exp (12)
'*' cargar y pasar al estado 14
'/' cargar y pasar al estado 15
'^' cargar y pasar al estado 16
$default reducir por 8 (exp)

---------------------------- ESTADO 20 --------------------------
exp -> exp . '+' exp (7)
exp -> exp '+' exp . (7)
exp -> exp . '-' exp (8)
exp -> exp . '*' exp (9)
exp -> exp . '/' exp (10)
exp -> exp . '^' exp (12)
'*' cargar y pasar al estado 14
'/' cargar y pasar al estado 15
'^' cargar y pasar al estado 16
Anlisis Sintctico en Procesadores de Lenguaje
105
$default reducir por 7 (exp)

---------------------------- ESTADO 21 --------------------------
exp -> exp . '+' exp (7)
exp -> exp . '-' exp (8)
exp -> exp . '*' exp (9)
exp -> exp '*' exp . (9)
exp -> exp . '/' exp (10)
exp -> exp . '^' exp (12)
'^' cargar y pasar al estado 16
$default reducir por 9 (exp)

---------------------------- ESTADO 22 --------------------------
exp -> exp . '+' exp (7)
exp -> exp . '-' exp (8)
exp -> exp . '*' exp (9)
exp -> exp . '/' exp (10)
exp -> exp '/' exp . (10)
exp -> exp . '^' exp (12)
'^' cargar y pasar al estado 16
$default reducir por 10 (exp)

---------------------------- ESTADO 23 --------------------------
exp -> exp . '+' exp (7)
exp -> exp . '-' exp (8)
exp -> exp . '*' exp (9)
exp -> exp . '/' exp (10)
exp -> exp . '^' exp (12)
exp -> exp '^' exp . (12)
'^' cargar y pasar al estado 16
$default reducir por 12 (exp)

---------------------------- ESTADO 24 --------------------------
$ cargar y pasar al estado 25

---------------------------- ESTADO 25 --------------------------
NO HAY ACCIONES
En primer lugar saca la lista de conflictos shift/reduce que aparecieron en la gramtica
y fueron resueltos utilizando las precedencias y asociatividades de los tokens y las reglas
y la de los que no pudieron resolverse, indicando el nmero del estado en el que
aparecieron. A continuacin aparece una lista en la que se relacionan los cdigos externos
Anlisis Sintctico en Procesadores de Lenguaje
106
de los tokens con sus nombres. El token $ se define automticamente y se utiliza para
representar el final de la entrada.
Despus saca informacin acerca de todos los estados del analizador generado. Dentro
del apartado dedicado a cada estado aparecen en primer lugar los items que lo forman
(cada estado es un conjunto de items) indicando entre parntesis el nmero de la regla de
la que proceden, seguido de las acciones a tomar para cada lookahead posible en ese
estado y por ltimo la reduccin por defecto si la hay. Si en un estado se produjeron
conflictos y no fueron resueltos, se elegir cargar si el conflicto es shift/reduce o reducir
por una de todas las reglas posibles si es reduce/reduce. Todas las dems reducciones (que
nunca se llevarn a cabo porque se escogi o bien cargar o reducir utilizando otra regla)
aparecern entre corchetes. En este caso no hay ninguna porque no qued ningn
conflicto sin resolver.
6.8.5.1.11 Depuracin en tiempo de ejecucin
Los analizadores sintcticos generados por yacc se pueden depurar en tiempo de
ejecucin por medio de la declaracin de la macro YYDEBUG o por medio del uso de la
opcin -t en las opciones de llamada a yacc.
%{
#define YYDEBUG 1
%}
Tambin se puede utilizar la variable entera yydebug con valor 1 antes de que el
cdigo llame a yyparse().
Estas opciones de depuracin imprimirn las acciones que se van realizando. Puede
que en la mayor parte de las veces genere una informacin excesivamente amplia. Sin
embargo en muchos casos es el nico camino para detectar errores recalcitrantes.
6.8.5.1.12 Ejemplos
Se va a desarrollar una pequeaa aplicacin utilizando Yacc. Ser un programa que
analice y ejecute expresiones aritmticas. Est basado en el HOC de Kernighan y Pike
[KERN84, captulo 8] y se va ha desarrollar en varias etapas.
Calculadora elemental
Lo primero que hay que hacer es definir la gramtica, que tiene que ser libre de
contexto en un fichero al que se denominar calculad.y. Se analizar por separado cada
lnea y se ejecutar antes de pasar a la siguiente, y cada lnea estar compuesta por
expresiones relacionadas entre s seguidas de un carcter nueva lnea, por lo tanto
podemos escribir las siguientes reglas de produccin:
input ::= /* vacio */
| input linea
;
linea ::= '\n'
| exp '\n'
| error '\n'
Anlisis Sintctico en Procesadores de Lenguaje
107
;
exp ::= NUM
| exp '+' exp
| exp '-' exp
| exp '*' exp
| exp '/' exp
| '-' exp
| exp '^' exp /* para exponentes */
| '(' exp ')'
;
Ahora habr que aadir la seccin de declaraciones en donde se define el tipo de la pila
de valores semnticos como double y los tokens que aparecen en la gramtica indicando
su precedencia y asociatividad. Ser:
%{
#define YYSTYPE double
#include <math.h>
%}
%token NUM
%left '-' '+'
%left '*' '/'
%left NEG /* Para darle precedencia al menos unario. */
%right '^'
Aadindole a cada regla las acciones que deben ejecutarse cada vez que se reduce
utilizndolas:
%%
input: /* vacio */
| input linea
;
linea: '\n'
| exp '\n' { printf("\t%.10g\n", $1); }
| error '\n' { yyerrok; }
;
exp: NUM { $$ = $1; }
| exp '+' exp { $$ = $1 + $3; }
| exp '-' exp { $$ = $1 - $3; }
| exp '*' exp { $$ = $1 * $3; }
| exp '/' exp { $$ = $1 / $3; }
| '-' exp %prec NEG { $$ = -$2; }
| exp '^' exp { $$ = pow ($1, $3); }
| '(' exp ')' { $$ = $2; }
Anlisis Sintctico en Procesadores de Lenguaje
108
;
%%
Con %prec NEG se indica que la regla que lo lleva debe tener la precedencia que se le
ha asignado al token NEG en la seccin de declaraciones, que es mayor que la de sumas,
restas, productos y divisiones.
Por ltimo habr que aadir el cdigo adicional, que en este caso consistir en un
sencillsimo analizador lxico que reconoce nmeros y operadores aritmticos, una rutina
de errores que saca un mensaje de error en el caso de que se produzca alguno, y una
funcin main que se ocupa de llamar a yyparse:
/* analizador lxico */
#include <ctype.h>
yylex ()
{
int c;
while ((c = getchar()) == ' ' || c == '\t') ;
if (c == '.' || isdigit (c))
{
ungetc(c, stdin);
scanf ("%lf", &yylval);
return NUM;
}
if (c == EOF) return 0;
return c;
}

/* principal */
main()
{
yyparse();
}

/* subrutina de error */
#include <stdio.h>
yyerror (s)
char *s;
{
printf ("%s\n", s);
}
El fichero que se obtiene como resultado es el que aparece en el anexo II. Una vez
compilado podr ser utilizado para calcular expresiones aritmticas.
Anlisis Sintctico en Procesadores de Lenguaje
109
Calculadora elemental con tratamiento de errores
Se mejora la calculadora anterior para dar tratamiento de errores, indicndose el
nmero de lnea y el nombre del programa.
%{
#define YYSTYPE double /* tipo de datos de la pila de yaccov */
#include <math.h>
#include <stdio.h>
void yyerror(char*);
void aviso (char *s, char *t);
char *nombreprograma; /* nombre de programa para mensajes de error */
int nlinea=1; /* nmero de lnea para mensajes de error */
%}
%token NUM
%left '-' '+'
%left '*' '/'
%left NEG /* Para darle precedencia al menos unario. */
%right '^'

%%
input: /* vacio */
| input linea
;
linea: '\n'
| exp '\n' { printf("\t%.10g\n", $1); }
| error '\n' { yyerrok; }
;
exp: NUM { $$ = $1; }
| exp '+' exp { $$ = $1 + $3; }
| exp '-' exp { $$ = $1 - $3; }
| exp '*' exp { $$ = $1 * $3; }
| exp '/' exp { $$ = $1 / $3; }
| '-' exp %prec NEG { $$ = -$2; }
| exp '^' exp { $$ = pow ($1, $3); }
| '(' exp ')' { $$ = $2; }
;
%%
/* analizador lxico */
#include <ctype.h>
int yylex (void)
{
Anlisis Sintctico en Procesadores de Lenguaje
110
int c;
while ((c = getchar()) == ' ' || c == '\t') ;
if (c == '.' || isdigit (c))
{
ungetc(c, stdin);
scanf ("%lf", &yylval);
return NUM;
}
if (c == EOF) return 0;
return c;
}
/* principal */
void main(int argc, char *argv[])
{
nombreprograma=argv[0];
printf("\nCalculadora 1: Escriba una expresin aritmtica y pulse
<intro>");
printf("\nOperadores permitidos: +,-,*,/,^,- unario, y ()");
printf("\nNuevo tratamiento de errores");
printf("\nPulse <control>-C (EOF) para salir\n");
yyparse();
}
/*subrutinas de error: se dejan preparadas para futuras versiones */
void yyerror (char *s)
{
aviso(s, (char *) 0 );
}
void aviso (char *s, char *t)
{
fprintf(stderr, "%s: %s", nombreprograma, s);
if (t) fprintf(stderr, " %s", t);
fprintf(stderr, " cerca de la lnea %d\n", nlinea);
}
Calculadora con variables
Se realiza una nueva revisin para incluir: 26 variables de la a la z, tratamiento de
errores de divisin por cero, operador asignacin (que permite asignaciones mltiples en
una lnea, por ejemplo x=y=z=0). Tambin se ilustra el uso de %union y de %token con
tipo.
%{
/* Calcula2.y */
Anlisis Sintctico en Procesadores de Lenguaje
111
double mem[26]; /* Memoria para las variables 'a'..'z' */
#include <math.h>
#include <stdio.h>
void yyerror(char*);
void error_en_float(void);
%}
%union { /* tipo de la pila */
double valor; /* valor actual */
int indice; /* ndice de mem[] */
}
%token <valor> NUM
%token <indice> VAR
%type <valor> exp
%right '='
%left '-' '+'
%left '*' '/'
%left NEG /* Para darle precedencia al menos unario. */
%right '^'

%%
input: /* vacio */
| input linea
;
linea: '\n'
| exp '\n' { printf("\t%.10g\n", $1); }
| error '\n' { yyerrok; }
;
exp: NUM { $$ = $1; }
| VAR { $$ = mem[$1]; }
| VAR '=' exp { $$ = mem[$1] = $3; }
| exp '+' exp { $$ = $1 + $3; }
| exp '-' exp { $$ = $1 - $3; }
| exp '*' exp { $$ = $1 * $3; }
| exp '/' exp {
if ($3==0.0) yyerror("Divisin por cero");
$$ = $1 / $3; }
| '-' exp %prec NEG { $$ = -$2; }
| exp '^' exp { $$ = pow ($1, $3); }
| '(' exp ')' { $$ = $2; }
;

Anlisis Sintctico en Procesadores de Lenguaje
112
%%
/* analizador lxico */
#include <ctype.h>
int yylex (void)
{
int c;
while ((c = getchar()) == ' ' || c == '\t');
/* Salta espacios en blanco */
if (c == '.' || isdigit (c))
{
ungetc(c, stdin);
scanf ("%lf", &yylval.valor);
return NUM;
}
if (islower(c))
{
yylval.indice=c-'a';
return VAR;
}
if (c == EOF) return 0;
return c;
}
/* principal */
#include <signal.h>
#include <setjmp.h>
jmp_buf inicio;
void main(void)
{
printf("\nCalculadora 2: Escriba una expresin aritmtica
y pulse <intro>");
printf("\nOperadores permitidos: +,-,*,/,^,- unario, y ()");
printf("\nNuevas opciones:");
printf("\n\tPermite uso de variables desde 'a' a 'z'");
printf("\n\tComprueba la divisin por cero");
printf("\n\tRecupera errores de punto flotante en ejecucin");
printf("\nPulse <control>-C (EOF) para salir\n");
setjmp(inicio);
signal(SIGFPE, error_en_float);
yyparse();
}
/* subrutina de error */
Anlisis Sintctico en Procesadores de Lenguaje
113
void yyerror (char *s)
{
printf ("%s\n", s);
longjmp(inicio,0);
}
void error_en_float(void)
{
yyerror("Error trabajando con nmeros reales");
}

Calculadora con funciones
Se puede ampliar la calculadora del ejemplo anterior aadindole funciones
predefinidas como seno, coseno, etc. Tambin se puede conseguir sin mucho trabajo que
permita declarar variables y almacenar valores en ellas. Esto significa que el analizador
lxico yylex tendr que reconocer identificadores y por lo tanto ser necesaria una tabla
de smbolos donde se almacenen tanto las variables como las funciones predefinidas.
La seccin de declaraciones en este caso ser:
%{
#include <math.h>
#include <ctype.h>
#include <stdio.h>
#include "calc.h"
%}
%union
{
double val; /* Devolver nmeros */
ptabla *tptr; /* Punteros a la tabla de smbolos */
}
%token <val> NUM
%token <tptr> VAR FUNC
%type <val> exp

%right '='
%left '-' '+'
%left '*' '/'
%left NEG /* Para el menos unario */
%right '^' /* Exponentes */

%%
Anlisis Sintctico en Procesadores de Lenguaje
114
En este caso los valores semnticos de los smbolos pueden ser de dos tipos: val y
ptabla, por lo que habr que indicar cual es el que le corresponde a cada uno. El fichero
incluido "calc.h" comtiene la definicin del tipo de la tabla de smbolos, que ser una lista
encadenada de registros. El contenido de este fichero ser:
typedef
struct ptabla
{
char *nombre; /* nombre del smbolo */
int tipo; /* tipo del smbolo */
union
{
double var; /* valor de una VAR */
double (*pfuncion)(); /* valor de una FUNC */
} valor;
struct ptabla *sig; /* puntero al siguiente elemento */
}
ptabla;
extern ptabla *stabla;
ptabla *ponsimb();
ptabla *buscasimb();
Las dos funciones que se definen son las que tratan la tabla de smbolos. La definicin
de la gramtica es muy similar a la del ejemplo anterior. Slo hay que aadir tres reglas
que hacen que la calculadora acepte variables y funciones. Ser:
%%
input: /* vacio */
| input linea
;
linea: '\n'
| exp '\n' { printf("\t%.10g\n", $1); }
| error '\n' { yyerrok; }
;
exp: NUM { $$ = $1; }
| VAR { $$ = $1->valor.var; }
| VAR '=' exp { $$ = $3;
$1->valor.var = $3; }
| FUNC '(' exp ')' { $$=(*($1->valor.pfuncion))($3); }
| exp '+' exp { $$ = $1 + $3; }
| exp '-' exp { $$ = $1 - $3; }
| exp '*' exp { $$ = $1 * $3; }
| exp '/' exp { $$ = $1 / $3; }
| '-' exp %prec NEG { $$ = -$2; }
Anlisis Sintctico en Procesadores de Lenguaje
115
| exp '^' exp { $$ = pow ($1, $3); }
| '(' exp ')' { $$ = $2; }
;
%%
Por la definicin de la gramtica se puede ver que la nueva calculadora admite
asignaciones mltiples y funciones anidadas.
En el cdigo C adicional, adems de las funciones main, yylex e yyerror aparecern
otras dos: ponsimb y buscasimb que son las que manejan la tabla de smbolos. La funcin
yylex tiene que ser modificada para que pueda reconocer identificadores. Resultar:
/* analizador lxico */
yylex ()
{
int c;

/* Saltar los espacios en blanco. */
while ((c = getchar()) == ' ' || c == '\t') ;
if (c == EOF) return 0;
if (c == '.' || isdigit (c)) /* Se ha encontrado un nmero */
{
ungetc(c, stdin);
scanf ("%lf", &yylval);
return NUM;
}
if (isalpha(c) /* Se ha encontrado un identificador. */
{
ptabla *s;
static char *buf = 0;
static int long = 0;
int i;
if (long == 0)
{
long = 40;
buf = (char *) malloc (long + 1);
}
i = 0;
do
{ /* Si el buffer est lleno, hacerlo mayor. */
if (i == long)
{
long *= 2;
Anlisis Sintctico en Procesadores de Lenguaje
116
buf = (char *) realoc(buff, long + 1);
}
/* Aadir este car cter al buffer. */
buf[i++] = c;
/* Leer otro car cter */
c = getchar;
}
while (c != EOF && isalnum (c));
ungetc(c,stdin);
buf[i] = '\0';
s = buscasimb(buf)
if (s == 0)
s = ponsimb(buf, VAR);
yylval.tptr = s;
return s->tipo;
}
/* Cualquier otro car cter es un token. */
return c;
}

/* Funcin principal */
main()
{
initabla();
yyparse();
}

/* Subrutina de error */
yyerror (s)
char *s;
{
printf ("%s\n", s);
}
struct ini
{
char *fnombre;
double (*func)();
};
struct ini farit[] =
{
"sen", sen,
Anlisis Sintctico en Procesadores de Lenguaje
117
"cos", cos,
"atan", atan,
"ln", log,
"exp", exp,
"sqrt", sqrt,
0, 0
};
ptabla *stabla = (ptabla *)0;
/* Pone en la tabla las funciones aritmticas */
initabla()
{
int i;
ptabla *ptr;

for (i=0; farit[i].fnombre != 0; i++)
{
ptr = ponsimb(farit[i].fnombre, FUNC);
ptr->valor.pfuncion = farit[i].func;
}
}
ptabla *ponsimb(snombre, stipo)
char *snombre;
int stipo;
{
ptabla *ptr;

ptr = (ptabla *) malloc (sizeof(ptabla));
ptr->nombre = (char *) malloc (strlen(snombre)+1);
strcpy(ptr->nombre, snombre);
ptr->tipo = stipo;
ptr->valor.var = 0;
ptr->sig = (struct ptabla *) stabla;
stabla = ptr;
return ptr;
}
ptabla *buscasimb(snombre)
char *snombre;
{
ptabla *ptr;

for (ptr=stabla; ptr!=(ptabla *) 0; ptr=(ptabla *)ptr->sig)
Anlisis Sintctico en Procesadores de Lenguaje
118
if (strcmp(ptr->nombre, snombre) == 0)
return ptr;
return 0;
}

Calculadora HOC
Se puede seguir ampliando la calculadora de los ejemplos anteriores aadindole ms
operadores (como AND, OR, >, <, etc), sentencias como if, while, funciones y
procedimientos, obtenindose el lenguaje HOC definido por Kernighan y Pike [KERN84,
captulo 8]. La entrada al yacc es la que viene a continuacin:
%{
#include "hoc.h"
#define code2(c1,c2) code(c1); code(c2)
#define code3(c1,c2,c3) code(c1); code(c2); code(c3)
%}
%union
{
Symbol *sym; /* puntero a la tabla de simbolos */
Inst *inst /* instruccin de mquina */
int narg /* nmero de argumentos */
}
%token <sym> NUMBER STRING PRINT VAR BLTIN UNDEF WHILE IF ELSE
%token <sym> FUNCTION PROCEDURE RETURN FUNC PROC READ
%token <narg> ARG
%type <inst> expr stmt asgn prlist stmtlist
%type <inst> cond while if begin end
%type <sym> procname
%type <narg> arglist
%right '='
%left OR
%left AND
%left GT GE LT LE EQ NE
%left '+' '-'
%left '*' '/'
%left UNARYMINUS NOT
%right '^'
%%
list: /* vaco */
| list '\n'
| list defn '\n'
Anlisis Sintctico en Procesadores de Lenguaje
119
| list asgn '\n' { code2(pop, STOP); return 1; }
| list stmt '\n' { code(STOP); return 1; }
| list expr '\n' { code2(print, STOP); return 1;}
| list error '\n' { yyerrok; }
;
asgn: VAR '=' expr { code3(varpush,(Inst)$1,assign); $$=$3; }
| ARG '=' expr { defnonly("$"); code2(argassign,(Inst)$1);
$$=$3; }
;
stmt: expr { code(pop); }
| RETURN { defnonly("return"); code(procret); }
| RETURN expr { defnonly("return"); $$=$2; code (funcret); }
| PROCEDURE begin '(' arglist ')'
{ $$ = $2; code3(call, (Inst)$1, (Inst)$4); }
| PRINT prlist {$$ = $2; }
| while cond stmt end
{ ($1)[1] = (Inst)$3; /* cuerpo del ciclo */
($1)[2] = (Inst)$4; } /* fin si la condicin no se cumple */
| if cond stmt end /* if sin else */
{ ($1)[1] = (Inst)$3; /* parte then */
($1)[3] = (Inst)$4; } /* fin si la condicin no se cumple */
| if cond stmt end ELSE stmt end /*if con else */
{ ($1)[1] = (Inst)$3; /* parte then */
($1)[2] = (Inst)$6; /* parte else */
($1)[3] = (Inst)$7; } /* fin si la condicin no se cumple */
| '{' stmtlist '}' { $$ = $2; }
;
cond: '(' expr ')' { code(STOP); $$ = $2; }
;
while: WHILE { $$ = code3(whilecode,STOP,STOP); }
;
if: IF { $$ = code(ifcode); code3(STOP,STOP,STOP); }
;
begin: /* vaco */ { $$ = progp; }
;
end: /* vaco */ { code(STOP); $$ = progp; }
;
stmtlist: /* vaco */ { $$ = progp; }
| stmtlist '\n'
| stmtlist stmt
;
Anlisis Sintctico en Procesadores de Lenguaje
120
expr: NUMBER { $$ = code2(constpush, (Inst)$1); }
| VAR { $$ = code3(varpush, (Inst)$1, eval); }
| ARG { defnonly("$"); $$ = code2(arg, (Inst)$1); }
| asgn
| FUNCTION begin '(' arglist ')'
{ $$ = $2; code3(call, (Inst)$1, (Inst)$4); }
| READ '(' VAR ')' { $$ = code2(varread, (Inst)$3); }
| BLTIN '(' expr ')' { $$=$3; code2(bltin, (Inst)$1->u.ptr); }
| '(' expr ')' { $$ = $2; }
| expr '+' expr { code(add); }
| expr '-' expr { code(sub); }
| expr '*' expr { code(mul); }
| expr '/' expr { code(div); }
| expr '^' expr { code(power); }
| '-' expr %prec UNARYMINUS { $$=$2; code(negate); }
| expr GT expr { code(gt); }
| expr GE expr { code(ge); }
| expr LT expr { code(lt); }
| expr LE expr { code(le); }
| expr EQ expr { code(eq); }
| expr NE expr { code(ne); }
| expr AND expr { code(and); }
| expr OR expr { code(or); }
| NOT expr { $$ = $2; code(not); }
;
prlist: expr { code(prexpr); }
| STRING { $$ = code2(prstr, (Inst)$1); }
| prlist ',' expr { code(prexpr); }
| prlist ',' STRING { code2(prstr, (Inst)$3); }
;
defn: FUNC procname { $2->type=FUNCTION; indef=1; }
'(' ')' stmt { code(procret); define($2); indef=0; }
| PROC procname { $2->type=PROCEDURE; indef=1; }
'(' ')' stmt { code(procret); define($2); indef=0; }
;
procname: VAR
| FUNCTION
| PROCEDURE
;
arglist: /* vaco */ { $$ = 0; }
| expr { $$ = 1; }
Anlisis Sintctico en Procesadores de Lenguaje
121
| arglist ',' expr { $$ = $1 + 1; }
;
%% /* fin de la gramtica */
#include <stdio.h>
#include <ctype.h>
#include <signal.h>
#include <setjmp.h>
char *progname;
int lineno = 1;
jmp_buf begin;
int indef;
char *infile; /* nombre de archivo de entrada */
FILE *fin /* apuntador a archivo de entrada */
char **gargv; /* lista global de argumentos */
int gargc;
int c; /* global, para uso de warning() */
yylex()
{
while ((c=getc(fin)) == ' ' || c== '\t') ;
if (c == EOF)
return 0;
if (c == '.' || isdigit(c)) /* nmero */
{
double d;
ungetc(c, fin);
fscanf(fin, "%lf", &d);
yylval.sym = install("", NUMBER, d);
return NUMBER;
}
if (isalpha(c))
{
Symbol *s;
char sbuf[100], *p =sbuf;
do
{
if (p >= sbuf + sizeof(sbuf) - 1)
{
*p = '\0';
execerror("nombre demasiado largo", sbuf);
}
*p++ =c;
Anlisis Sintctico en Procesadores de Lenguaje
122
} while ((c=getc(fin) != EOF && isalnum(c));
ungetc(c, fin);
*p = '\0';
if ((s=lookup(sbuf)) == 0)
s = install(sbuf, UNDEF, 0.0);
yylval.sym =s;
return s->type == UNDEF ? VAR : s->type;
}
if (c == '$') /* argumento? */
{
int n = 0;
while (isdigit(c=getc(fin)))
n = 10 * n + c - '0';
ungetc(c,fin);
if (n == 0)
excerror("strange $...", (char *)0);
yylval.narg = n;
return ARG;
}
if (c == '"') /* cadena entre comillas */
{
char sbuf[100], *p, *emalloc();
for (p = sbuf; (c=getc(fin)) !='"'; p++)
{
if (c == '\n' || c==EOF)
execerror("comilla no encontrada","");
if (p >= sbuf + sizeof(sbuf) -1
{
*p = '\0';
execerror("cadena demasiado larga", sbuf);
}
*p = backslash(c);
}
*p = 0;
yylval.sym = (Symbol *)emalloc(strlen(sbuf)+1);
strcpy(yylval.sym,sbuf);
return STRING;
}
switch (c)
{
case '>' : return follow('=', GE, GT);
Anlisis Sintctico en Procesadores de Lenguaje
123
case '<' : return follow('=', LE, LT);
case '=' : return follow('=', EQ, '=');
case '!' : return follow('=', NE, NOT);
case '|' : return follow('|', OR, '|');
case '&' : return follow('&', AND,'&');
case '\n': lineno++; return '\n';
default: return c;
}
}
backslash(c) /* tomar el siguiente caracter con las \ interpretadas*/
int c;
{
char *index(); /* ' strchr()' en algunos sistemas */
static char transtab[]="b\bf\fn\nr\rt\t";
if (c!='\\') return c;
c = getc(fin);
if (islower(c) && index(transtab,c))
return index (transtab,c)[1];
return c;
}
follow(expect,ifyes, ifno) /* bsqueda hacia adelante para >=, etc */
{
int c=getc(fin);
if (c == expect)
return ifyes;
ungetc(c,fin);
return ifno;
}
defnonly(s) /* advertencia si hay definicin ilegal */
char *s;
{
if (!indef) execerror(s,"definicin ilegal");
}
yyerror(s)/* comunicar errores de tiempo de compilacin */
char *s;
{
warning(s, (char *)0);
}
execerror(s,t) /* recuperacin de errores de tiempo de ejecucin */
char *s, *t;
{
Anlisis Sintctico en Procesadores de Lenguaje
124
warning(s,t);
fseek(fin,OL,2); /* sacar el resto del archivo */
longjmp(begin,0);
}
fpecatch() /* detectar errores por punto flotante */
{
execerror("error de punto flotante", (char *)0);
}
main(argc,argv)
char *argv[];
{
int i,fpecatch();

progname = argv[0];
if (argc == 1) /* simular una lista de argumentos */
{
static char *stdinonly[] = {"-"};
gargv = stdinonly;
gargc = 1;
}
else
{
gargv = argv +1;
gargc = argc - 1;
}
init();
while (moreinput())
run();
return 0;
}
moreinput()
{
if (gargc-- <= 0)
return 0;
if (fin && fin != stdin)
fclose(fin);
infile = *gargv++;
lineno = 1;
if (strcmp(infile, "-")) == 0)
{
fin = stdin;
Anlisis Sintctico en Procesadores de Lenguaje
125
infile = 0;
}
else if ((fin=fopen(infile, "r")) ==NULL)
{
fprintf(stderr,"%s: can't open %s\n",progname,infile);
return moreinput();
}
return 1;
}
run() /* ejecutar hasta el fin de archivo (EOF) */
{
setjmp(begin);
signal(SIGFPE, fpecatch);
for (initcode(); yyparse(); initcode())
execute(progbase);
}
warning(s,t) /* imprimir mensaje de advertencia */
char *s, *t;
{
fprintf(stderr, "%s: %s", progname, s);
if (t) fprintf(stderr, " %s",t);
if (infile) fprintf(stderr, " in %s", infile);
fprintf(stderr, " en la lnea %d\n", lineno);
while (c != '\n' && c != EOF)
c = getc(fin); /* sacar el resto del rengln de entrada +/
if (c == '\n') lineno++;
}
Compilador de MUSIM/1 a ENSAMPOCO/1
Se construye un compilador de MUSIM/1 a ENSAMPOCO/1, ambos definidos en
[CUEV94b].
%{
/* cmusim1.y */
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <mem.h>
int nlinea=1;
FILE *yyin,*yyout;
void yyerror(char*);
int yyparse(void);
Anlisis Sintctico en Procesadores de Lenguaje
126
void genera(char*);
void genera2(char*, int);
%}
%union
{ char identificador;
int constante;
}
%token <constante> NUM
%token <identificador> VAR
%type <constante> bloque sentencia lectura escritura asignacion
%right '='
%left '-' '+'
%left '*' '/' '%'
%left NEG /* Para darle precedencia al menos unario. */
%right '^'
%start programa
%%
programa : 'M' '{' bloque finBloque
;
bloque : { genera(".CODE"); } instrucciones
;
finBloque : '}' {genera("END");}
;
instrucciones : /* vaco */
| instrucciones sentencia
| instrucciones sentencia ';'
;
sentencia : lectura
| escritura
| asignacion
;
lectura : 'R' VAR { genera2("INPUT", $2); }
;
escritura : 'W' VAR { genera2("OUTPUT", $2); }
;
asignacion : VAR { genera2("PUSHA", $1); }
'=' exp { genera("STORE"); }
;
exp : NUM { genera2("PUSHC", $1); }
| VAR { genera2("PUSHA", $1);
genera("LOAD"); }
Anlisis Sintctico en Procesadores de Lenguaje
127
| exp '+' exp { genera("ADD"); }
| exp '-' exp { genera("NEG");
genera("ADD"); }
| exp '*' exp { genera("MUL"); }
| exp '/' exp { genera("DIV"); }
| exp '%' exp { genera("MOD"); }
| '-' exp %prec NEG { genera("NEG"); }
| exp '^' exp { genera("EXP"); }
| '(' exp ')'
;
%%
/* analizador lxico */
#include <ctype.h>
int yylex (void)
{
int c;
while ((c = getc(yyin)) != EOF)
{
if (c == ' '| c == '\t') continue;
/* Elimina blancos y tabuladores */
if (c == '\n')
{++nlinea;
continue; /* Elimina los caracteres fin de lnea */
}
if (isdigit (c))
{
yylval.constante = c;
return NUM;
}
if (islower(c))
{
yylval.identificador = c;
return VAR;
}
return c;
}
return 0;
}
/* principal */
void main(int argc, char *argv[])
{
Anlisis Sintctico en Procesadores de Lenguaje
128
if ((argc<3) || (argc>4))
{
printf("\nCompilador MUSIM/1-ENSAMPOCO/1 desarrollado
con Yacc");
printf("\nForma de uso:");
printf("\ncmusim1 fich_fuente fich_destino\n");
exit(-1);
}
if ((yyin=fopen(argv[1],"r")) == NULL)
{
printf("\nNo se puede abrir el fichero -> %s\n", argv[1]);
exit(-2);
}

if ((yyout=fopen(argv[2],"w")) == NULL)
{
printf("\nNo se puede abrir el fichero -> %s\n", argv[2]);
exit(-3);
}
printf("\nTraduciendo ...\n");
yyparse();
fcloseall();
exit(0);
}
/* subrutina de error */
void yyerror (char *s)
{
fprintf (stdout,"Error en lnea %d : %s\n", nlinea, s);
}
void genera(char *s)
{
fprintf(yyout,"%s\n",s);
}
void genera2(char *s, int x)
{
fprintf(yyout,"%s %c\n", s, x);
}

Anlisis Sintctico en Procesadores de Lenguaje
129
6.8.5.2 Antlr
El sistema PCCTS (Purdue Compiler Construction Tool Set) [Parr97] fue escrito
inicialmente en el lenguaje C++ para generar compiladores escritos tambin en C++.
Posteriormente fue portado al lenguaje Java pasndose a llamar ANTLR 2.x.x [Inst01]
ANTLR es slo una de las tres partes del sistema que est compuesto por:
Antlr (Another Tool for Language Recognition). Una herramienta para
construir procesadores de lenguajes a partir de una descripcin gramatical.
DLG (DFA-based Lexical analyzer Generator). Es el generador de
analizadores lxicos basados en autmatas finitos deterministas.
SORCERER. Herramienta para construir rboles sintcticos abstractos
(ASTs).
Caractersticas generales
Es una herramienta creada para el mismo propsito que Lex/YACC. Ambas tienen
bastantes cosas en comn, aunque se podra decir que ANTLR es ms fcil de usar y se
integra mejor con lenguajes orientados a objeto como C++ y Java.
La facilidad de uso de ANTLR se fundamenta en que utiliza analizadores descendentes
recursivos LL(k) cuyo funcionamiento interno es mucho ms predecible y fcil de
depurar que las tablas de anlisis LALR(1).
Las caractersticas ms importantes que aporta esta herramienta son:
1. Predicados semnticos.
2. Predicados sintcticos
3. Construccin de rboles sintcticos abstractos.

Predicados semnticos
Un predicado semntico, especifica una condicin que debe ser cumplida en tiempo de
ejecucin, antes de que el analizador pueda continuar. Existen dos tipos de predicados
semnticos, los que realizan la funcin de validacin lanzando una excepcin si no se
cumplen las condiciones necesarias cuando se analiza una produccin, y los que resuelven
problemas de ambigedades. Los predicados semnticos se representan mediante
acciones semnticas.
El siguiente ejemplo muestra el uso de un predicado semntico para validar si un
identificador es semnticamente un tipo nombre.
decl: "var" ID ":" t:ID
{ isTypeName(t.getText()) }?
;
A travs del predicado semntico se genera una excepcin si no se cumple la
condicin.
Anlisis Sintctico en Procesadores de Lenguaje
130
El siguiente ejemplo muestra el uso de un predicado semntico para distinguir entre
una declaracin de variables y una sentencia de asignacin.
Statement:
{isTypeName(
1
LT(1))}? ID ID ; // Declaracin
ID = expr ;; // Asignacin
;
El analizador generado realizar la siguiente interpretacin:
if(
2
LA(1)==ID && isTypeName(LT(1)))
{
match alternative one
}
else if (LA(1)==ID)
{
match alternative two
}
else error
Se demuestra as como se puede influir en el proceso de anlisis con informacin
semntica mediante los predicados semnticos.

Predicados sintcticos
Los predicados sintcticos son usados durante el anlisis cuando no es posible resolver
la ambigedad producida en dos o ms alternativas de una produccin. Un predicado
sintctico ejecuta las llamadas de funcin del predicado, pero no ejecuta el cdigo de
accin asociado a la regla. Si se satisface el predicado, se elige la alternativa
correspondiente y se ejecuta el cdigo, sino se procesa la siguiente alternativa (o
predicado) que lo cumpla. Los predicados sintcticos son implementados usando las
excepciones del lenguaje de implementacin.
El siguiente ejemplo muestra una regla que distingue entre una lista y una lista
asignada a otra lista.
stat: ( list "=" )=> list "=" list
| list
;
Si se encuentra una lista seguida por un operador de asignacin, entonces se elige la
primera produccin, sino se intenta el anlisis eligiendo la segunda alternativa. Los
predicados sintcticos aportan una forma de realizar un anlisis con retroceso

1
El token i-simo
2
Es el tipo de token del i-simo smbolo
Anlisis Sintctico en Procesadores de Lenguaje
131
(backtraking) selectivo en el que no se ejecutan las acciones durante el proceso de
evaluacin.
rboles sintcticos abstractos
La construccin de rboles sintcticos abstractos (ASTs) se realiza aumentando la
especificacin gramatical con operadores, reglas y acciones. ANTLR tambin permite
especificar la estructura gramatical de los ASTs, mediante una coleccin de reglas EBNF
mezcladas con acciones, predicados semnticos y predicados sintcticos, de forma
anloga a la herramienta SORCERER cuando se usa de forma separada.
El siguiente ejemplo muestra la construccin de un analizador para una pequea
calculadora de expresiones, generando un AST como representacin intermedia y un
analizador que recorre dicha representacin intermedia evaluando el resultado.
class CalcParser extends Parser;
options {
buildAST = true; // usa CommonAST por defecto
}
expr: mexpr (PLUS^ mexpr)* SEMI!
; //!Operador para indicar que el
AST // token ser ignorado para el AST
mexpr: atom (STAR^ atom)*
;
atom: INT
;
Los tokens PLUS y STAR representan operadores y se indica que sern nodos raz del
rbol que se va a generar mediante el carcter ^. El smbolo ! se usa para indicar que
el token SEMI, que representa el carcter ;, no se incluir en el AST.
Para construir un analizador que permita recorrer los rboles generados a partir de las
expresiones de entrada, es necesario especificar la estructura gramatical del AST.
class CalcTreeWalker extends TreeParser;
expr : #(PLUS expr expr)
| #(STAR expr expr)
| INT
;
Una vez especificada la estructura, es necesario aadir acciones que permitan evaluar
el resultado de las expresiones.
class CalcTreeWalker extends TreeParser;
expr returns [float r]
{
float a,b;
r=0;
}
Anlisis Sintctico en Procesadores de Lenguaje
132
: #(PLUS a=expr b=expr) {r = a+b;}
| #(STAR a=expr b=expr) {r = a*b;}
| i:INT {r = (float)Integer.parseInt(i.getText());}
;
Los smbolos de entrada son almacenados como nodos en el rbol, pero la estructura es
codificada teniendo en cuenta la relacin de esos nodos.
Por ltimo, el cdigo necesario para iniciar el proceso es el siguiente:
import java.io.*;
import antlr.CommonAST;
import antlr.collections.AST;

class Calc {
public static void main(String[] args) {
try {
CalcLexer lexer =
new CalcLexer(new DataInputStream(System.in));
CalcParser parser = new CalcParser(lexer);
// Analiza la expresin de la entrada
parser.expr();
CommonAST t = (CommonAST)parser.getAST();
// Imprime el AST usando la notacin LISP
System.out.println(t.toStringList());
CalcTreeWalker walker = new CalcTreeWalker();
// Recorre el rbol creado por el analizador
int r = walker.expr(t);
System.out.println("value is "+r);
} catch(Exception e) {
System.err.println("exception: "+e);
}
}
}

La entrada de Antlr
La descripcin de entrada en Antlr consiste en una coleccin de reglas lxicas y
sintcticas describiendo el lenguaje que va a ser reconocido, y un conjunto de acciones
semnticas definidas por el usuario para realizar el tratamiento correspondiente cuando
las instrucciones de entrada son reconocidas. Formalmente el fichero de especificacin
contiene la siguiente informacin:
Seccin de cabecera
Definicin de reglas lxicas
Anlisis Sintctico en Procesadores de Lenguaje
133
Definicin de reglas sintcticas
Definicin de reglas para generar ASTs (Esta seccin es opcional)
Acciones

Seccin de cabecera
Es la primera seccin del fichero y contiene el cdigo fuente que debe estar colocado
en la salida, antes que el cdigo generado por Antlr. Sirve por ejemplo, para especificar
las libreras y las clases importadas en el lenguaje Java.
Definicin de reglas lxicas
Las reglas lxicas deben comenzar con una letra mayscula, pueden especificar
argumentos, devolver valores y usar variables locales. Son procesadas de la misma forma
que las reglas sintcticas.
Definicin de reglas sintcticas
Las reglas sintcticas deben comenzar con una letra minscula y siguen un formato
anlogo a la notacin utilizada en YACC, aunque en este caso se permite la notacin
EBNF.
Definicin de reglas para generar ASTs
En la definicin de reglas que generan un AST, es necesario utilizar una sintaxis
adicional y especial para especificar la estructura del rbol generado. No obstante, las
especificaciones en los tres tipos de definiciones de reglas son similares.
Acciones
Son bloques de cdigo fuente ejecutados durante el anlisis, despus de que los
elementos de una produccin hayan sido reconocidos y antes de reconocer el siguiente
elemento situado a su derecha. Si la accin ocupa el primer elemento de una produccin,
se ejecuta antes de procesar la regla.
El siguiente ejemplo muestra los tres tipos de especificaciones gramaticales (lxicas,
sintcticas y AST) necesarias para analizar expresiones aritmticas sencillas.
class CalcParser extends Parser;
options {
buildAST = true; // usa CommonAST por defecto
}
expr
: mexpr (PLUS^ mexpr)* SEMI!
;
mexpr
: atom (STAR^ atom)*
;
atom: INT
Anlisis Sintctico en Procesadores de Lenguaje
134
;

class CalcLexer extends Lexer;
WS : (' '
| '\t'
| '\n'
| '\r')
{ $setType = Token.SKIP; }
;
LPAREN: '('
;
RPAREN: ')'
;
STAR: '*'
;
PLUS: '+'
;
SEMI: ';'
;
protected
DIGIT
: '0'..'9'
;
INT : (DIGIT)+
;

class CalcTreeWalker extends TreeParser;
expr returns [float r]
{
float a,b;
r=0;
}
: #(PLUS a=expr b=expr) {r = a+b;}
| #(STAR a=expr b=expr) {r = a*b;}
| i:INT {r = (float)Integer.parseInt(i.getText());}
;
Las especificaciones en Antlr siguen un estilo similar a las de YACC. Incluyen la
gramtica del lenguaje que debe reconocer el analizador que se va a generar, y al lado de
cada una de las producciones de la gramtica, aparece el cdigo de las acciones
semnticas en el lenguaje de implementacin utilizado C/C++ o Java. Tambin se incluye
la especificacin del analizador lxico, definindose los tokens necesarios para ser
utilizados por el analizador sintctico.
El usuario deber proporcionar, adems del fichero de especificaciones, un objeto con
un mtodo main() que se encargue de inicializar una instancia del analizador lxico y del
analizador sintctico.


Anlisis Sintctico en Procesadores de Lenguaje
135
La salida de Antlr
Antlr genera tres tipos de ficheros MY_PARSER, MY_LEXER, y
MY_TREE_PARSER. Son nombres genricos ya que en realidad se asignan los nombres
de las clases contenidas en el fichero de especificaciones.
El fichero MY_LEXER contiene la clase que describe las reglas del analizador
lxico. Su funcin consiste en dividir la secuencia de caracteres de entrada en tokens para
ser suministrados al analizador sintctico.
El fichero MY_PARSER contiene la clase con los mtodos de las reglas del
analizador sintctico. Esta clase generada contiene un mtodo para cada una de las reglas
de la gramtica y es subclase de LlkParser, que es la clase padre de todos los analizadores
sintcticos que genera Antlr. Como realiza un anlisis descendente, comienza el proceso
siempre desde un smbolo inicial y va derivando hasta encontrar los tokens que le
proporciona el analizador lxico.
El fichero MY_TREE_PARSER contiene la clase que representa el AST (Abstract
Syntax Tree). Los ASTs permiten construir un rbol de derivacin genrico que podr ser
aplicado a un programa concreto expresado en un lenguaje de programacin determinado.
Sobre este AST se podr aplicar una o varias clases (tree-walkers) que se encarguen de
recorrer el rbol.
Tratamiento de errores
El mecanismo de tratamiento y recuperacin de errores en Antlr es bastante bueno. Se
establece sobre la base del sistema de tratamiento de excepciones presente en Java y en
otros lenguajes de programacin como C++.
Soporta mecanismos de tratamiento y recuperacin de errores a travs de los bloques
de cdigo try/catch que especifica el usuario al lado de determinados elementos de inters
de la gramtica. Si en el proceso de derivacin se produce un error, se lanza una
excepcin que es tratada por alguna de las acciones semnticas asociadas con la seccin
gramatical que invoc al mtodo en donde se produjo el error.
Si no se incluye un tratamiento de errores explcito, las excepciones sern propagadas
y tratadas de manera simple, con el cdigo que proporciona por defecto la herramienta.
Los analizadores sintcticos generados pueden lanzar excepciones para sealar
determinados problemas o errores encontrados. El mecanismo de tratamiento de errores
se basa en la siguiente jerarqua de excepciones.


Anlisis Sintctico en Procesadores de Lenguaje
136

ANTLRException
CharStreamException
RecognitionException
TokenStreamException
CharStreamIOException
MismatchedCharException
MismatchedTokenException
NoViableAltException
NoViableAltForCharException
SemanticException
TokenStreamIOException
TokenStreamRecognitionException
TokenStreamRetryException


ANTLRException. Es la raz de la jerarqua de excepciones.
CharStreamException. Indica que hubo problemas leyendo del fichero que
contiene la especificacin de entrada.
RecognitionException. Seala un error genrico cada vez que hay un problema
reconociendo la entrada. Todas las reglas gramaticales lanzan esta excepcin
TokenStreamException. Indica que ocurre algn problema en la formacin de
los tokens.
CharStreamIOException. Indica problemas de entrada/salida.
MismatchedCharException. Seala que el carcter ledo no es el esperado.
MismatchedTokenException. Seala que el smbolo terminal no es el esperado.
NoViableAltException. Indica que el analizador sintctico no puede elegir entre
las alternativas de una regla, debido a que el token ledo, no se incluye en el comienzo de
ninguna de ellas.
NoViableAltForCharException. Indica que el analizador lxico no puede elegir
entre las alternativas de una regla, debido a que el carcter ledo, no se incluye en el
comienzo de ninguna de ellas.
SemanticException. Esta excepcin se lanza automticamente cuando se
encuentran problemas con los predicados semnticos.
TokenStreamIOException. Indica que se encontraron problemas durante la
generacin de una secuencia de tokens en la entrada/salida.
TokenStreamRecognitionException. Seala un error indicando que ocurre algn
problema en la formacin de los tokens.
Anlisis Sintctico en Procesadores de Lenguaje
137
TokenStreamRetryException. Seala que se abandona el reconocimiento del
token actual y se intenta conseguir otro nuevamente.
6.8.5.3 JavaCC
JavaCC [Java01] es un generador de analizadores sintcticos escritos en lenguaje Java.
Soporta bsicamente las mismas caractersticas que Antlr, aunque hay que tener en cuenta
que JavaCC es un producto comercial diseado para ser utilizado con aplicaciones Java,
del que no estn disponibles los ficheros fuentes, a diferencia de Antlr que es de dominio
pblico.
Caractersticas generales
Como caractersticas generales de la herramienta se destacan las siguientes:
1. Permite generar analizadores lxicos y analizadores sintcticos recursivos
descendentes LL(k).
2. Integra en la descripcin completa del fichero de entrada, la especificacin lxica y
la especificacin sintctica.
3. Incluye otra herramienta denominada JJTree que es un preprocesador de la entrada
de JavaCC y cuya funcin consiste en insertar acciones en determinados lugares de
la especificacin del fichero para generar rboles sintcticos abstractos (ASTs). La
salida de JJTree debe ser procesada por JavaCC para generar el analizador
sintctico. Los ASTs son muy utilizados fundamentalmente en analizadores que
deben procesar entradas complejas.
La entrada de JavaCC
El formato de entrada en JavaCC, contiene una coleccin de reglas lxicas y sintcticas
para describir el lenguaje que va a ser reconocido y un conjunto de acciones semnticas
definidas por el usuario para realizar las tareas correspondientes siempre y cuando las
instrucciones de entrada sean reconocidas. Formalmente, la descripcin del fichero de
especificaciones contiene las siguiente secciones:
Lista de opciones
Java Compilation Unit
Producciones de la gramtica

Lista de opciones
La lista de opciones puede estar o no presente en el fichero de entrada. Permite incluir
distintas informaciones mediante un conjunto de identificadores que son inicializados a
unos valores concretos.
Java Compilation Unit
Anlisis Sintctico en Procesadores de Lenguaje
138
Esta seccin incluye una JCU (Java compilation unit) delimitada por dos nombres que
actan como marcas, PARSER_BEGIN y PARSER_END. Contiene como mnimo la
declaracin de una clase cuyo nombre coincide con el del analizador sintctico generado.
JavaCC no realiza comprobaciones sobre esta unit, por tanto es posible generar ficheros
que produzca errores cuando sean compilados.
El analizador generado contiene todo el cdigo de esta unit y la declaracin de un mtodo
pblico para cada uno de los no terminales de la gramtica especificada. Estos mtodos
sern llamados posteriormente para realizar el proceso de anlisis.
Producciones de la gramtica
Existen cuatro clases de producciones: las de cdigo Java y notacin BNF, usadas para
definir la gramtica, las que describen las expresiones regulares y las que se usan para
introducir declaraciones.
Producciones de cdigo Java. Es una forma de escribir cdigo para algunas
producciones que tienen que reconocer una secuencia de tokens difciles de
describir mediante reglas gramaticales.
Producciones EBNF. Son las producciones estndar usadas para la descripcin
de la gramtica. Como cada smbolo no terminal se traduce a un mtodo en el
analizador generado, el nombre debe ser el mismo que el del mtodo, y los
parmetros y valores de retorno declarados, se utilizan para pasar valores hacia
arriba y hacia abajo en el rbol sintctico. La parte derecha de una produccin
comienza con un conjunto de declaraciones y cdigo, que es colocado al
comienzo del mtodo generado para el smbolo no terminal de la parte
izquierda. Cada vez que se usa el smbolo no terminal en el proceso de
anlisis, se ejecuta el cdigo pero JavaCC no procesa este cdigo y las
declaraciones incluidas en esta parte, es el compilador de Java el que detecta
posibles errores.
Producciones de expresiones regulares. Se usan para definir las entidades
lxicas que sern procesadas por el analizador lxico generado. Cada
produccin proporciona el estado lxico para el cual se aplica, la clase de
expresin regular y las especificaciones que describen de forma detallada las
entidades lxicas de dicha expresin regular junto con las acciones
correspondientes.
Producciones para incluir declaraciones. Contienen bloques de cdigo en Java
con declaraciones y sentencias accesibles desde el cdigo de las producciones
lxicas.
El siguiente ejemplo muestra las especificaciones necesarias para analizar expresiones
aritmticas sencillas, implementando una pequea calculadora.
PARSER_BEGIN(Calc0) // define la clase parser
public class Calc0 {
public static void main (String args []) {
Calc0 parser = new Calc0(System.in);
for (;;)
try {
if (parser.expr() == -1)
System.exit(0);
} catch (Exception e) {
Anlisis Sintctico en Procesadores de Lenguaje
139
e.printStackTrace(); System.exit(1);
}
}
}
PARSER_END(Calc0)

SKIP: // define la entrada que deber ser ignorada
{ " " | "\r" | "\t"
}
TOKEN: // define nombres de tokens
{ < EOL: "\n" >
| < CONSTANT: ( <DIGIT> )+ >
| < #DIGIT: ["0" - "9"] >
}

int expr(): // expr: sum \n
{} // -1 para eof, 0 para eol
{ sum() <EOL> { return 1; }
| <EOL> { return 0; }
| <EOF> { return -1; }
}
void sum(): // sum: product { +- product }
{}
{ product() ( ( "+" | "-" ) product() )*
}
void product(): // product: term { *%/ term }
{}
{ term() ( ( "*" | "%" | "/" ) term() )*
}
void term(): // term: +term | -term | (sum) | number
{}
{ "+" term()
| "-" term()
| "(" sum() ")"
| <CONSTANT>
}
En este ejemplo, no se ha incluido una lista de opciones. Se comienza con la descripcin
del cdigo que debe proveer el usuario para inicializar y terminar el proceso de anlisis,
es decir, la clase que contiene al analizador sintctico y que generar JavaCC a partir de la
especificacin del fichero de entrada.
En la seccin de producciones, se describen dos clases de expresiones regulares. SKIP,
contiene las expresiones que se corresponden con las partes de la entrada que se pueden
ignorar en el proceso de anlisis, como por ejemplo, comentarios. TOKEN, recoge las
expresiones asociadas a los tokens de la gramtica. A partir de esta informacin se genera
el analizador lxico.
El resto de la especificacin corresponde a la definicin de la gramtica. Para cada
smbolo no terminal de la gramtica se colocan las distintas producciones y al lado de
estas, el cdigo de accin. Se puede observar tambin que los smbolos no terminales se
Anlisis Sintctico en Procesadores de Lenguaje
140
definen como mtodos de la clase que se va a generar con los parmetros y valores de
retorno
Aunque en el ejemplo se ve como todos los componentes lxicos han sido definidos
previamente en la seccin correspondiente, tambin se pueden realizar definiciones de
este tipo dentro del mbito de una regla, aunque en este caso dicha definicin solo tendr
validez dentro de esa regla.
La salida de JavaCC
La salida de JavaCC incluye las siguientes clases:
TokenMgrError. Es la excepcin que se lanza cuando se detectan problemas
con el reconocimiento de los tokens. Tambin se encarga de que los mensajes
de error presenten suficiente informacin al usuario. Es una subclase de la
clase Error.
ParseException. Es la excepcin que se lanza cuando el analizador sintctico
encuentra algn problema. Es una subclase de la clase Exception.
Token. La clase que encapsula los smbolos terminales que se encarga de
obtener el analizador lxico para suministrrselos al analizador sintctico.
Parser. La clase del analizador sintctico principal.
ParserTokenManager. Un gestor de smbolos terminales para el analizador
contenido en el parser.
ParserConstants. La clase que define las constantes utilizadas por el
analizador sintctico.
La palabra parser en estas clases representa un nombre simblico, puesto que en realidad
dicho nombre es elegido por el usuario de la herramienta.
Tratamiento de errores
El tratamiento de errores de JavaCC est basado en el mecanismo de excepciones del
lenguaje Java. No existe una jerarqua de excepciones determinada, sino que cuando se
encuentra un error durante el proceso de anlisis, se lanza una excepcin de tipo
ParserException.
Esta excepcin podr ser tratada en el lugar donde se produjo o en cualquier otra
produccin que la pueda anteceder en el proceso de derivacin, que en el caso de un
analizador descendente comienza en un smbolo inicial y va derivando hasta que se
encuentran en la entrada los smbolos terminales.
El usuario podr utilizar otras excepciones adems de ParserException siempre y cuando
las haya definido previamente. El cdigo asociado al tratamiento de errores se incluye en
la propia especificacin de JavaCC, junto al cdigo de accin y al lado de la
especificacin sintctica.
Gracias a que JavaCC genera la clase TokenMgrError, es posible personalizar los
mensajes de error para que sean comprensibles para el usuario y le ayuden a resolver el
error que ha tenido lugar.
Anlisis Sintctico en Procesadores de Lenguaje
141
ANEXO I: ALGORITMO DE DECISIN
Para construir los conjuntos citados se ejecutan los siguientes pasos:
PASO 1: Encontrar los smbolos no terminales y producciones que sean anulables.
PASO 2: Construccin de la relacin EMPIEZA DIRECTAMENTE CON.
PASO 3: Calcular la relacin EMPIEZA CON.
PASO 4: Calcular el conjunto de INICIALES de cada no terminal.
PASO 5: Calcular el conjunto de INICIALES de cada produccin.
PASO 6: Construccin de la relacin ESTA DIRECTAMENTE SEGUIDO POR.
PASO 7: Construccin de la relacin ES FIN DIRECTO DE.
PASO 8: Construccin de la relacin ES FIN DE.
PASO 9: Construccin de la relacin ESTA SEGUIDO POR.
PASO 10: Calcular el conjunto de SEGUIDORES de cada no terminal anulable.
PASO 11: Calcular el conjunto de SIMBOLOS DIRECTORES.

PASO 1: Encontrar los smbolos no terminales y producciones anulables
Consiste en aplicar un algoritmo que comprueba qu no terminales generan la cadena
vaca. El algoritmo se basa en utilizar un vector cuyos ndices son elementos no
terminales, y los elementos del vector solamente pueden tomar estos tres valores:
SI (el smbolo no terminal SI genera )
NO (el no terminal NO genera )
INDECISO (no se sabe si genera )
Los pasos del algoritmo son:
4. Inicializar todos los elementos del vector con INDECISO.
5. Efectuar las siguiente acciones en la primera pasada de la gramtica:
1. Si una produccin de un no terminal es la cadena vaca, el correspondiente
elemento del vector toma el valor SI, y se eliminan de la gramtica todas las
producciones de dicho no terminal.
Se eliminan de la gramtica todos los no terminales cuyas producciones contengan un
smbolo terminal. Si la accin elimina todas las producciones de un no-terminal su
correspondiente valor del vector toma el valor NO.
Anlisis Sintctico en Procesadores de Lenguaje
142
6. En este momento, la gramtica est limitada a las producciones cuya parte derecha
contiene slo smbolos no terminales. En las siguientes pasadas se examinan cada
uno de los smbolos de la parte derecha.
1. Si para un no terminal de la parte derecha su elemento en el vector vale SI, se
elimina ese smbolo de la parte derecha. Si este deja como parte derecha la
cadena vaca, el elemento correspondiente del vector al no terminal de la parte
izquierda toma el valor SI, y se eliminan todas las producciones
correspondientes a dicho no terminal.
Si para un no terminal de la parte derecha su elemento del vector vale NO, se elimina la
produccin. Si todas las producciones de un no terminal se eliminan de esta manera, su
entrada en el vector toma el valor NO.
7. Si durante una pasada completa de la gramtica no se cambia ninguna entrada del
vector, y existen todava elementos del vector con el valor INDECISO, termina el
algoritmo y la gramtica no es LL(1).
Si una gramtica no pasa de este primer paso, es a la vez recursiva a izquierdas y
sucia.
1. Es sucia ya que existen producciones que constan slo de smbolos no
terminales que no pueden generar cadenas compuestas slo por terminales. Es
decir, hay smbolos no terminales muertos.
2. Es recursiva a izquierdas, puesto que son un conjunto finito y an quedan los
elementos del vector como INDECISOS, por tanto, los miembros ms a la
izquierda deben de formar un bucle.
Si la gramtica es limpia, siempre se podr generar un vector cuyos ndices son los no
terminales y los elementos son todos SI o NO..
Ejemplo de clculo de los smbolos anulables
Sea la gramtica G = (VT, VN, P, S) donde:
VN= {<E>, <A>, <B>, <C>, <D>}
VT= {+, id}
S= <E>
y las reglas de produccin P son:
<E> ::= <E>+<E> | <A> + <B> | id
<B> ::= <C><A>
<C> ::= <B>
<D> ::= <E>
<A> ::=
Se trata de aplicar el algoritmo expuesto en el epgrafe anterior.
1. Construccin del vector cuyos ndices son no terminales, y se inicializa a indeciso.
Anlisis Sintctico en Procesadores de Lenguaje
143
IND IND IND IND IND
<E> <A> <B> <C> <D>

Figura 16: Vector de los smbolos no terminales inicializados a INDECISO
2. a) La produccin <A> ::= <VACIO>, hace que el elemento correspondiente a <A>
tome el valor SI en el vector, y se eliminan todas las producciones de <A> que en este
caso slo es una.
IND SI IND IND IND
<E> <A> <B> <C> <D>

Figura 17: Vector de smbolos no terminales
En la gramtica slo quedan las producciones:
<E> ::= <E>+<E> | <A>+<B> |id
<B> ::= <C><A>
<C> ::= <B>
<D> ::= <E>
b) Se eliminan de la gramtica los no terminales cuyas producciones tengan un
smbolo terminal. En este caso es el <E>, y su correspondiente elemento del
vector toma el valor NO.
N O S I I N D I N D I N D
< E > < A > < B > < C > < D >

Figura 18: vector de smbolos no terminales
En la gramtica slo quedan las producciones:
<B> ::= <C><A>
<C> ::= <B>
<D> ::= <E>
8. En este momento slo hay producciones cuya parte derecha slo contiene smbolos
no terminales. Se examina a continuacin cada uno de los smbolos de la parte
derecha de cada produccin:
1. Se toma la produccin B:= <C><A> dado que <A> ya tiene el valor si se
elimina con lo que queda la gramtica:
<B>:= <C>
<C>:= <B>
<D>:= <E>
el vector no sufre cambios.
2. b) Se toma la produccin <D>::= <E> y dado que <E> tiene valor NO, se
elimina la produccin, y en el vector el elemento correspondiente a <D> toma
el valor NO y la gramtica queda:
<B>:= <C>
Anlisis Sintctico en Procesadores de Lenguaje
144
<C>:= <E>
N O S I I N D I N D N O
< E > < A > < B > < C > < D >

Figura 19: vector de smbolos no terminales
9. Si se da otra pasada a la gramtica, el vector no cambia, y quedan <B> y <C>
como indecisos. Luego la gramtica es sucia y recursiva a izquierdas como puede
observarse.
Tambin se puede utilizar el programa analizador de gramticas LL(1), en el cual la
gramtica debe introducirse en el formato siguiente pues no admite el smbolo alternativa.
<E> ::= <E> + <E>
<E> ::= <A> + <B>
<E> ::= id
<A> ::=
<B> ::= <C> <A>
<C> ::= <B>
<D> ::= <E>
El resultado que se obtiene en el programa es:
RELACION DE SIMBOLOS INDECISOS: B C
RELACION DE SIMBOLOS ANULABLES: A
RELACION DE SIMBOLOS NO ANULABLES: D E
RELACION DE SIMBOLOS MUERTOS: B C
RELACION DE SIMBOLOS NO ACCESIBLES: D
PASO 2: Construccin de la relacin EMPIEZA DIRECTAMENTE CON
Se define que A EMPIEZA DIRECTAMENTE CON B, si derivando A se puede
obtener B como principio de cadena derivada de A. Los no terminales anulables se
reemplazan por el vaco. A VN y B V
+.
Es decir, si y slo si existe una produccin de
la forma AB siendo:
A VN
una cadena anulable, es decir que puede derivar a vacio
B V
+

una cadena cualquiera
Ejemplo de clculo de la relacin EMPIEZA DIRECTAMENTE CON
Sea una gramtica elemental del lenguaje MUSIM.
<PROGRAMA> ::= <BLOQUE>.
<BLOQUE> ::= <SENTENCIA> <OTRA SENTENCIA>
<OTRA SENTENCIA> ::= <SENTENCIA> <OTRA SENTENCIA> | <vaco>
Anlisis Sintctico en Procesadores de Lenguaje
145
<SENTENCIA> ::= <ASIGNACION> | <LECTURA> | <ESCRITURA>
<ASIGNACION> ::= <VARIABLE> = <EXPRESION>
<EXPRESION> ::= <TERMINO><MAS TERMINOS>
<MAS TERMINOS> ::= + <TERMINO><MAS TERMINOS> |
- <TERMINO><MAS TERMINOS> |
<vaco>
<TERMINO> ::= <FACTOR><MAS FACTORES>
<MAS FACTORES> ::= * <FACTOR><MAS FACTORES> |
/ <FACTOR><MAS FACTORES> |
% <FACTOR><MAS FACTORES> |
<vaco>
<FACTOR> ::= (<EXPRESION>) | <VARIABLE> | <CONSTANTE>
<LECTURA> ::= R <VARIABLE>
<ESCRITURA> ::= W <VARIABLE>
<CONSTANTE> ::= dgito
<VARIABLE> ::= letra_minscula
<vaco> ::=
Determinar la relacin EMPIEZA DIRECTAMENTE POR.
<PROGRAMA> empieza directamente por <BLOQUE>
<BLOQUE> empieza directamente por <SENTENCIA>
<OTRA SENTENCIA> empieza directamente por ;
<SENTENCIA> empieza directamente por <ASIGNACION>
<LECTURA>
<ESCRITURA>
<ASIGNACION> empieza directamente por <VARIABLE>
<EXPRESION> empieza directamente por <TERMINO>
<MAS TERMINOS> empieza directamente por +
-
TERMINO> empieza directamente por <FACTOR>
<MAS FACTORES> empieza directamente por *
/
%
<FACTOR> empieza directamente por (
<VARIABLE>
<CONSTANTE>
<LECTURA> empieza directamente por R
<ESCRITURA> empieza directamente por W
Anlisis Sintctico en Procesadores de Lenguaje
146
<CONSTANTE> empieza directamente por dgito
<VARIABLE> empieza directamente por letra_minscula
Esta relacin se puede representar mediante la siguiente matriz EMPIEZA
DIRECTAMENTE CON
.
ASIGNACION
BLOQUE
CONSTANTE
ESCRITURA
EXPRESION
FACTOR
LECTURA
MAS_FACTORES
MAS_TERMINOS
OTRA_SENTENCIA
PROGRAMA
SENTENCIA
TERMINO
VARIABLE
R
W
%
(
*
+
-
.
/
;
=
)
dgito
letra_minscula
A
S
I
G
N
A
C
I
O
N
B
L
O
Q
U
E
C
O
N
S
T
A
N
T
E
E
S
C
R
I
T
U
R
A
E
X
P
R
E
S
I
O
N
F
A
C
T
O
R
L
E
C
T
U
R
A
M
A
S
_
F
A
C
T
O
R
E
S
M
A
S
_
T
E
R
M
I
N
O
S
O
T
R
A
_
S
E
N
T
E
N
C
I
A
P
R
O
G
R
A
M
A
S
E
N
T
E
N
C
I
A
T
E
R
M
I
N
O
V
A
R
I
A
B
L
E
RW%(*+-./;=)d

g
i
t
o
l
e
t
r
a
_
m
i
n

s
c
u
l
a
1
1
1
1
1
1 1 1
1
1 1 1
1 1
1
1
1 1 1
1
1

Figura 20: Matriz "EMPIEZA DIRECTAMENTE CON"
PASO 3: Construccin de la relacin EMPIEZA CON
Se define que A EMPIEZA CON B si y slo si existe una cadena derivada de A que
empiece con B. Tambin se admite que A EMPIEZA CON A. Se puede demostrar que la
relacin EMPIEZA CON es el cierre reflexivo transitivo de la relacin EMPIEZA
DIRECTAMENTE CON. Para determinar la nueva matriz de relacin se puede utilizar
el algoritmo de Warshal modificado
Ejemplo de clculo de la relacin EMPIEZA CON
Continuando con el ejemplo del paso anterior (apartado 5.4.4.2.1), se obtiene la matriz
EMPIEZA CON aplicando el algoritmo de Warshall modificado.
Anlisis Sintctico en Procesadores de Lenguaje
147
ASIGNACION
BLOQUE
CONSTANTE
ESCRITURA
EXPRESION
FACTOR
LECTURA
MAS_FACTORES
MAS_TERMINOS
OTRA_SENTENCIA
PROGRAMA
SENTENCIA
TERMINO
VARIABLE
R
W
%
(
*
+
-
.
/
;
=
)
dgito
letra_minscula
A
S
I
G
N
A
C
I
O
N
B
L
O
Q
U
E
C
O
N
S
T
A
N
T
E
E
S
C
R
I
T
U
R
A
E
X
P
R
E
S
I
O
N
F
A
C
T
O
R
L
E
C
T
U
R
A
M
A
S
_
F
A
C
T
O
R
E
S
M
A
S
_
T
E
R
M
I
N
O
S
O
T
R
A
_
S
E
N
T
E
N
C
I
A
P
R
O
G
R
A
M
A
S
E
N
T
E
N
C
I
A
T
E
R
M
I
N
O
V
A
R
I
A
B
L
E
RW%(*+-./;=)d

g
i
t
o
l
e
t
r
a
_
m
i
n

s
c
u
l
a
1
1
1
1
1
1 1 1
1
1 1 1
1 1
1
1
1 1 1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1 1 1 1 1 1
1
1
1 1 1 1 1 1
1 1
1 1 1 1 1 1 1
1 1 1
1
1
1 1 1 1 1

Figura 21: Matriz "EMPIEZA CON"
PASO 4: Clculo del conjunto de smbolos INICIALES de cada no terminal
Se pueden calcular por el mtodo visto en los apartados anteriores(5.4.2.2), o tomando
los smbolos terminales marcados con un 1 en cada fila de la matriz de la relacin
EMPIEZA CON.
Ejemplo de clculo del conjunto de smbolos INICIALES de cada no terminal
Continuando con el ejemplo de los apartados anteriores se obtiene:
INICIALES( <ASIGNACION> )= { letra_minscula }
INICIALES( <BLOQUE> ) = { R W letra_minscula }
INICIALES( <CONSTANTE> ) = { dgito }
INICIALES( <ESCRITURA> ) = { W }
INICIALES( <EXPRESION> ) = { ( dgito letra-minscula }
INICIALES( <FACTOR> ) = { ( dgito letra_minscula }
INICIALES( <LECTURA> ) = { R }
INICIALES( <MAS FACTORES> ) = { % * / }
INICIALES( <MAS TERMINOS> ) = { + - }
INICIALES( <OTRA SENTENCIA> ) = { ; }
INICIALES( <PROGRAMA> ) = { R W letra_minscula }
INICIALES( <SENTENCIA> ) = { R W letra_minscula }
INICIALES( <TERMINO> ) = { ( dgito letra_minscula }
INICIALES( <VARIABLE> ) = { letra_minscula }
Anlisis Sintctico en Procesadores de Lenguaje
148
PASO 5: Clculo de los conjuntos INICIALES de cada produccin
Se calculan los conjuntos de INICIALES de la cadena formada por la parte derecha de
cada produccin.
Ejemplo de clculo de los smbolos INICIALES de cada produccin
Continuando con los ejemplos anteriores se obtiene:
INICIALES( <VARIABLE> = <EXPRESION> ) = { letra_minscula }
INICIALES( <SENTENCIA> <OTRA SENTENCIA> ) = { R W
letra_minscula }
INICIALES( dgito ) = { dgito }
INICIALES( W <VARIABLE> ) = { W }
INICIALES( <TERMINO> <MAS TERMINOS> ) = { ( dgito
letra_minscula }
INICIALES( ( <EXPRESION> ) = { ( }
INICIALES( <VARIABLE> ) = { letra_minscula }
INICIALES( <CONSTANTE> ) = { dgito }
INICIALES( <R <VARIABLE> ) = { R }
INICIALES( * <FACTOR> <MAS FACTORES> ) = { * }
INICIALES( / <FACTOR> <MAS FACTORES> ) = { / }
INICIALES( % <FACTOR> <MAS FACTORES> ) = { % }
INICIALES( + <TERMINO> <MAS TERMINOS> ) = { + }
INICIALES( - <TERMINO> <MAS TERMINOS> ) = { - }
INICIALES( ; <SENTENCIA> <OTRA SENTENCIA> ) = { ; }
INICIALES( <BLOQUE> ) = { R Wletra_minscula }
INICIALES( <ASIGNACION> ) = { letra_minscula }
INICIALES( <LECTURA> ) = { R }
INICIALES( <ESCRITURA> ) = { W }
INICIALES( <FACTOR> <MAS FACTORES> ) = { ( dgito
letra_minscula }
INICIALES( letra_minscula ) = { letra_minscula }
INICIALES( <vaco> ) = { }
PASO 6: Construccin de la relacin ESTA SEGUIDO DIRECTAMENTE
POR
Se define que A est-seguido-directamente-por B si y slo si existe una produccin de
la forma:
D AB
donde:
A, B V
+

es una cadena anulable
Anlisis Sintctico en Procesadores de Lenguaje
149
, son cadenas cualesquiera
Ejemplo de clculo de la relacin ESTA SEGUIDO DIRECTAMENTE POR
Entonces continuando el ejemplo de los epgrafes anteriores se tiene que:
<PROGRAMA> ::= <BLOQUE> .
<BLOQUE> est-seguido-directamente-por .
<BLOQUE> ::= <SENTENCIA> <OTRA SENTENCIA>
<SENTENCIA> est-seguido-directamente-por <OTRA SENTENCIA>
<OTRA SENTENCIA> ::= ; <SENTENCIA> <OTRA SENTENCIA> | <vaco>
; est-seguido-directamente-por <SENTENCIA>
<SENTENCIA> est-seguido-directamente-por <OTRA_SENTENCIA>
<ASIGNACION> ::= <VARIABLE> = <EXPRESION>
<VARIABLE> est-seguido-directamente-por =
= est-seguido-directamente-por <EXPRESION>
<EXPRESION> ::= <TERMINO> <MAS TERMINOS>
<TERMINO> est-seguido-directamente-por <MAS TERMINOS>
<MAS TERMINOS> ::= + <TERMINO> <MAS TERMINOS> |
- <TERMINO> <MAS TERMINOS> |
<vaco>
+ est-seguido-directamente-por <TERMINO>
- est-seguido-directamente-por <TERMINO>
<TERMINO> est-seguido-directamente-por <MAS TERMINOS>
<TERMINO> ::= <FACTOR> <MAS FACTORES>
FACTOR est-seguido-directamente-por <MAS FACTORES>
<MAS FACTORES> ::= * <FACTOR> <MAS FACTORES> |
/ <FACTOR> <MAS FACTORES> |
% <FACTOR> <MAS FACTORES> |
<vaco>
* est-seguido-directamente-por <FACTOR>
/ est-seguido-directamente-por <FACTOR>
% est-seguido-directamente-por <FACTOR>
<FACTOR> est-seguido-directamente-por <MAS FACTORES>
Anlisis Sintctico en Procesadores de Lenguaje
150
<FACTOR> ::= ( <EXPRESION> ) | <VARIABLE> | <CONSTANTE>
( est-seguido-directamente-por <EXPRESION>
<EXPRESION> est-seguido-directamente-por )
<LECTURA> ::= R <VARIABLE>
R est-seguido-directamente-por <VARIABLE>
<ESCRITURA> ::= W <VARIABLE>
W est-seguido-directamente-por <VARIABLE>
Representndolo matricialmente:
ASIGNACION
BLOQUE
CONSTANTE
ESCRITURA
EXPRESION
FACTOR
LECTURA
MAS_FACTORES
MAS_TERMINOS
OTRA_SENTENCIA
PROGRAMA
SENTENCIA
TERMINO
VARIABLE
R
W
%
(
*
+
-
.
/
;
=
)
dgito
letra_minscula
A
S
I
G
N
A
C
I
O
N
B
L
O
Q
U
E
C
O
N
S
T
A
N
T
E
E
S
C
R
I
T
U
R
A
E
X
P
R
E
S
I
O
N
F
A
C
T
O
R
L
E
C
T
U
R
A
M
A
S
_
F
A
C
T
O
R
E
S
M
A
S
_
T
E
R
M
I
N
O
S
O
T
R
A
_
S
E
N
T
E
N
C
I
A
P
R
O
G
R
A
M
A
S
E
N
T
E
N
C
I
A
T
E
R
M
I
N
O
V
A
R
I
A
B
L
E
RW%(*+-./;=)d

g
i
t
o
l
e
t
r
a
_
m
i
n

s
c
u
l
a
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1

Figura 22: Matriz "ESTA SEGUIDO DIRECTAMENTE POR"
PASO 7: Construccin de la relacin ES FIN DIRECTO DE
Se define A ES FIN DIRECTO DE B, si y slo si existe una produccin de la forma:
B A
donde A, B son smbolos terminales o no terminales, es anulable y es una cadena
cualesquiera.
Ejemplo de clculo de la relacin ES FIN DIRECTO DE
Continuando con el ejemplo de los epgrafes anteriores se obtiene que:
Anlisis Sintctico en Procesadores de Lenguaje
151
<PROGRAMA> no es fin directo de ningn smbolo
<BLOQUE> no es fin directo de ningn smbolo
<OTRA SENTENCIA> es fin directo de <BLOQUE> y <OTRA
SENTENCIA>
<SENTENCIA> es fin directo de <BLOQUE> y <OTRA SENTENCIA>
<ASIGNACION> es fin directo de <SENTENCIA>
<EXPRESION> es fin directo de <ASIGNACION>
<MAS TERMINOS> es fin directo de <EXPRESION> y <MAS TERMINOS>
<TERMINO> es fin directo de <EXPRESION> y <MAS TERMINOS>
<MAS FACTORES> es fin directo de <MAS FACTORES> y
<TERMINO>
<FACTOR> es fin directo de <MAS FACTORES> y <TERMINO>
<LECTURA> es fin directo de <SENTENCIA>
<ESCRITURA> es fin directo de <SENTENCIA>
<CONSTANTE> es fin directo de <FACTOR>
<VARIABLE> es fin directo de <FACTOR>, <LECTURA> y
<ESCRITURA>
. es fin directo de <PROGRAMA>
) es fin directo de <FACTOR>
dgito es fin directo de <CONSTANTE>
letra_minscula es fin directo de <VARIABLE>

Los dems smbolos terminales no son fin directo de ningn smbolo.
ASIGNACION
BLOQUE
CONSTANTE
ESCRITURA
EXPRESION
FACTOR
LECTURA
MAS_FACTORES
MAS_TERMINOS
OTRA_SENTENCIA
PROGRAMA
SENTENCIA
TERMINO
VARIABLE
R
W
%
(
*
+
-
.
/
;
=
)
dgito
letra_minscula
A
S
I
G
N
A
C
I
O
N
B
L
O
Q
U
E
C
O
N
S
T
A
N
T
E
E
S
C
R
I
T
U
R
A
E
X
P
R
E
S
I
O
N
F
A
C
T
O
R
L
E
C
T
U
R
A
M
A
S
_
F
A
C
T
O
R
E
S
M
A
S
_
T
E
R
M
I
N
O
S
O
T
R
A
_
S
E
N
T
E
N
C
I
A
P
R
O
G
R
A
M
A
S
E
N
T
E
N
C
I
A
T
E
R
M
I
N
O
V
A
R
I
A
B
L
E
RW%(*+-./;=)d

g
i
t
o
l
e
t
r
a
_
m
i
n

s
c
u
l
a
1
1
1
1
1 1
1
1 1
1 1
1 1
1 1
1 1
1 1 1
1
1
1
1

Figura 23: Matriz "ES FIN DIRECTO DE"
Anlisis Sintctico en Procesadores de Lenguaje
152
PASO 8: Construccin de la relacin ES FIN DE
Se define la relacin A ES FIN DE B, si y slo si existe una cadena que pueda
derivarse de B y que termina con A. La relacin ES FIN DE es el cierre reflexivo de la
relacin ES FIN DIRECTO DE. Para calcular la matriz que representa esta relacin se
aplica el algoritmo de Wharsall modificado a la matriz que representa la relacin ES
FIN DIRECTO DE.
ASIGNACION
BLOQUE
CONSTANTE
ESCRITURA
EXPRESION
FACTOR
LECTURA
MAS_FACTORES
MAS_TERMINOS
OTRA_SENTENCIA
PROGRAMA
SENTENCIA
TERMINO
VARIABLE
R
W
%
(
*
+
-
.
/
;
=
)
dgito
letra_minscula
A
S
I
G
N
A
C
I
O
N
B
L
O
Q
U
E
C
O
N
S
T
A
N
T
E
E
S
C
R
I
T
U
R
A
E
X
P
R
E
S
I
O
N
F
A
C
T
O
R
L
E
C
T
U
R
A
M
A
S
_
F
A
C
T
O
R
E
S
M
A
S
_
T
E
R
M
I
N
O
S
O
T
R
A
_
S
E
N
T
E
N
C
I
A
P
R
O
G
R
A
M
A
S
E
N
T
E
N
C
I
A
T
E
R
M
I
N
O
V
A
R
I
A
B
L
E
RW%(*+-./;=)d

g
i
t
o
l
e
t
r
a
_
m
i
n

s
c
u
l
a
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1 1 1 1
1 1
1 1 1 1 1
1
1
1 1
1 1 1
1 1 1 1 1 1 1
1 1 1
1 1 1 1 1 1 1
1 1 1 1 1
1
1 1
1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1
1
1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1

Figura 24: Matriz "ES FIN DE"
PASO 9: Construccin de la relacin ESTA SEGUIDO POR
Se define que A ESTA SEGUIDO POR B, si y slo si existe una cadena que pueda
derivarse del smbolo inicial de la gramtica, en la que aparece inmediatamente seguida
por B. La relacin ESTA SEGUIDO POR es el producto de las relaciones ES FIN DE,
ESTA SEGUIDO DIRECTAMENTE POR y EMPIEZA CON. La matriz que representa
la relacin ESTA SEGUIDO POR se obtiene como producto de las matrices de las
relaciones indicadas anteriormente.
Anlisis Sintctico en Procesadores de Lenguaje
153
ASIGNACION
BLOQUE
CONSTANTE
ESCRITURA
EXPRESION
FACTOR
LECTURA
MAS_FACTORES
MAS_TERMINOS
OTRA_SENTENCIA
PROGRAMA
SENTENCIA
TERMINO
VARIABLE
R
W
%
(
*
+
-
.
/
;
=
)
dgito
letra_minscula
A
S
I
G
N
A
C
I
O
N
B
L
O
Q
U
E
C
O
N
S
T
A
N
T
E
E
S
C
R
I
T
U
R
A
E
X
P
R
E
S
I
O
N
F
A
C
T
O
R
L
E
C
T
U
R
A
M
A
S
_
F
A
C
T
O
R
E
S
M
A
S
_
T
E
R
M
I
N
O
S
O
T
R
A
_
S
E
N
T
E
N
C
I
A
P
R
O
G
R
A
M
A
S
E
N
T
E
N
C
I
A
T
E
R
M
I
N
O
V
A
R
I
A
B
L
E
RW%(*+-./;=)d

g
i
t
o
l
e
t
r
a
_
m
i
n

s
c
u
l
a
1
1 1 1 1
1
1
1 1 1 1
1
1 1
1
1
1 1
1 1 1 1 1 1 1 1 1
1
1
1
1 1
1 1
1 1 1
1 1 1
1 1 1 1
1 1 1 1
1 1 1
1 1 1 1 1 1 1
1 1 1 1 1
1 1 1 1
1 1 1 1
1 1 1 1 1 1 1 1 1
1 1
1
1 1 1 1 1 1 1
1 1
1 1 1
1 1 1 1 1 1 1
1 1
1 1 1 1 1
1 1 1
1
1 1
1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1
1
1
1 1 1
1 1 1
1 1 1
1 1 1
1 1 1
1 1 1
1
1 1 1
1 1 1 1 1 1 1
1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1

Figura 25: Matriz "ESTA SEGUIDO POR"
PASO 10: Calcular el conjunto de seguidores de cada no terminal anulable
El conjunto de seguidores de cada no terminal anulable, se obtiene examinando las
filas correspondientes a los no terminales anulables (determinados en el paso 1), de la
matriz de la relacin ESTA SEGUIDO POR, obtenida en el paso anterior.
PASO 11: Calcular el conjunto de smbolos directores
El conjunto de smbolos directores se calcula a partir de su definicin, por medio de
los conjuntos de smbolos Iniciales (obtenido en el paso 5) y Seguidores (obtenido en el
paso 10). Por ltimo slo queda comprobar que los conjuntos de smbolos directores de la
expansin de cada no terminal, son conjuntos disjuntos.
El algoritmo de decisin, est implementado en la Universidad de Oviedo en el
programa ANALIZADOR DE GRAMATICAS LL(1), disponible para ordenadores bajo
MS-DOS.

ANEXO II: ANALIZADOR SINTCTICO GENERADO CON
Yacc

#define NUM 258
#define NEG 259
#line 1 "calculad.y"
Anlisis Sintctico en Procesadores de Lenguaje
154
#define YYSTYPE double
#include <math.h>
#ifndef YYLTYPE
typedef
struct yyltype
{
int timestamp;
int first_line;
int first_column;
int last_line;
int last_column;
char *text;
}
yyltype;
#define YYLTYPE yyltype
#endif
#define YYACCEPT return(0)
#define YYABORT return(1)
#define YYERROR goto yyerrlab
#ifndef YYSTYPE
#define YYSTYPE int
#endif
#include <stdio.h>
#ifndef __STDC__
#define const
#endif
#define YYFINAL 25
#define YYFLAG -32767
#define YYNTBASE 13
#define YYTRADUCE(x) ((unsigned)(x) <= 259 ? yytraduce[x] : 16)
static const char yytraduce[] = { 0,
2, 2, 2, 2, 2, 2, 2, 2, 2, 10,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 11,
12, 6, 5, 2, 4, 2, 7, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 9, 2, 2, 2, 2, 2, 2,
Anlisis Sintctico en Procesadores de Lenguaje
155
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 1, 2, 3, 8
};
static const short yyrlinea[] = { 0,
14, 15, 18, 19, 20, 23, 24, 25, 26, 27,
28, 29, 30
};
static const char * const yytnom[] = { 0,
"error","$ilegal.","NUM","'-'","'+'","'*'","'/'","NEG","'^'","'\\n'",
"'('","')'","input"
};
static const short yyr1[] = { 0,
13, 13, 14, 14, 14, 15, 15, 15, 15, 15,
15, 15, 15
};
static const short yyr2[] = { 0,
0, 2, 1, 2, 2, 1, 3, 3, 3, 3,
2, 3, 3
};
static const short yydefacc[] = { 1,
0, 0, 6, 0, 3, 0, 2, 0, 5, 11,
0, 0, 0, 0, 0, 0, 4, 13, 8, 7,
9, 10, 12, 0, 0
};
static const short yydefgoto[] = { 1,
7, 8
};
Anlisis Sintctico en Procesadores de Lenguaje
156
static const short yypacc[] = {-32767,
3, -9,-32767, 18,-32767, 18,-32767, 21,-32767, 10,
11, 18, 18, 18, 18, 18,-32767,-32767, 26, 26,
10, 10, 10, 5,-32767
};
static const short yypgoto[] = {-32767,
-32767, -4
};
#define YYLAST 35
static const short yytabla[] = { 10,
9, 11, 24, 2, 25, 3, 4, 19, 20, 21,
22, 23, 5, 6, 12, 13, 14, 15, 16, 16,
3, 4, 18, 0, 12, 13, 14, 15, 6, 16,
17, 14, 15, 0, 16
};
static const short yycheq[] = { 4,
10, 6, 0, 1, 0, 3, 4, 12, 13, 14,
15, 16, 10, 11, 4, 5, 6, 7, 9, 9,
3, 4, 12, -1, 4, 5, 6, 7, 11, 9,
10, 6, 7, -1, 9
};
#define YYPURE 1
#line 2 "simple.prs"
/* Estructura para el analizador generado */
#define yyerrok (yyerrstatus = 0)
#define yyclearin (yychar = YYEMPTY)
#define YYEMPTY -2
#define YYEOF 0
#define YYFAIL goto yyerrlab;
#define YYTERROR 1
#ifndef YYIMPURE
extern int yylex( void) ;
#define YYLEX yylex()
#endif
#ifndef YYPURE
extern int yylex( YYLTYPE *, YYLTYPE *) ;
#define YYLEX yylex(&yylval, &yylloc)
#endif
#ifndef YYIMPURE
int yychar; /* contiene el token lookahead */
YYSTYPE yylval; /* contiene el valor semntico del */
Anlisis Sintctico en Procesadores de Lenguaje
157
/* token lookahead. */
YYLTYPE yylloc; /* contiene la localizacin del token */
/* lookahead. */
int yynerr; /* nmero de errores hasta ahora. */

#endif /* YYIMPURE */

/*YYMAXPROF indica el tamao inicial de las pilas del analizador */
#ifndef YYMAXPROF
#define YYMAXPROF 200
#endif

/* YYMAXLIMIT es el tamao mximo que pueden alcanzar las pilas. */
#ifndef YYMAXLIMIT
#define YYMAXLIMIT 10000
#endif

#line 167 "simple.prs"
int yyparse()
{
register int yystate;
register int yyn;
register short *yyssp;
register YYSTYPE *yyvsp;
YYLTYPE *yylsp;
int yyerrstatus; /* nmero de tokens a cargar antes de volver a */
/* emitir mensajes de error. */
int yychar1; /* contiene el cdigo interno del lookahead. */
short yyssa[YYMAXPROF]; /* pila de estados */
YYSTYPE yyvsa[YYMAXPROF]; /* pila de valores semnticos */
YYLTYPE yylsa[YYMAXPROF]; /* pila de localizacin */
short *yyss = yyssa;
YYSTYPE *yyvs = yyvsa;
YYLTYPE *yyls = yylsa;
int yymaxprof = YYMAXPROF;
#ifndef YYPURE
int yychar;
YYSTYPE yylval;
YYLTYPE yylloc;
#endif
YYSTYPE yyval; /* devuelve valores sem nticos de las */
Anlisis Sintctico en Procesadores de Lenguaje
158
/* acciones. */
int yylen;
#ifdef YYDEBUG
fprintf(stderr, "Empieza el anlisis\n");
#endif

yystate = 0;
yyerrstatus = 0;
yynerr = 0;
yychar = YYEMPTY; /* Hace que se lea un token. */
/* Inicializar los punteros a las pilas. */
yyssp = yyss - 1;
yyvsp = yyvs;
yylsp = yyls;
/* Cargar un nuevo estado, que se encuentra en yystate.*/
/* Siempre que se llega aqu las pilas de valores y localizacin */
/* acaban de recibir una carga por lo que al cargar un estado,
/* quedan niveladas.*/
yynewstate:
*++yyssp = yystate;
if (yyssp >= yyss + yymaxprof - 1)
{
/* Darle al usuario la oportunidad de relocalizar la pila. */
YYSTYPE *yyvs1 = yyvs;
YYLTYPE *yyls1 = yyls;
short *yyss1 = yyss;
int size = yyssp - yyss + 1;
#ifdef yyoverflow
yyoverflow("overflow",
&yyss1, size * sizeof (*yyssp),
&yyvs1, size * sizeof (*yyvsp),
&yyls1, size * sizeof (*yylsp),
&yymaxprof);
yyss = yyss1; yyvs = yyvs1; yyls = yyls1;
#else
if (yymaxprof >= YYMAXLIMIT)
yyerror("overflow");
yymaxprof *= 2;
if (yymaxprof > YYMAXLIMIT)
yymaxprof = YYMAXLIMIT;
yyss = (short *) malloc (yymaxprof * sizeof (*yyssp));
Anlisis Sintctico en Procesadores de Lenguaje
159
memcpy ((char *)yyss, (char *)yyss1, size * sizeof (*yyssp));
yyvs = (YYSTYPE *) malloc (yymaxprof * sizeof (*yyvsp));
memcpy ((char *)yyvs, (char *)yyvs1, size * sizeof (*yyvsp));
#ifdef YYLSP_NEEDED
yyls = (YYLTYPE *) malloc (yymaxprof * sizeof (*yylsp));
memcpy ((char *)yyls, (char *)yyls1, size * sizeof (*yylsp));
#endif
#endif /* yyoverflow */
yyssp = yyss + size - 1;
yyvsp = yyvs + size - 1;
#ifdef YYLSP_NEEDED
yylsp = yyls + size - 1;
#endif
#ifdef YYDEBUG
fprintf(stderr, "Se ha aumentado la pila hasta %d\n",
yymaxprof);
#endif
if (yyssp >= yyss + yymaxprof - 1)
YYABORT;
}
#ifdef YYDEBUG
fprintf(stderr, "Estado %d\n", yystate);
#endif
/* Ejecutar las acciones correspondientes para el estado actual */
/* Leer un lookahead si es necesario.*/
yyresume:
/* Antes de nada, intentar decidir qu hacer sin utilizar el */
/* lookahead */
yyn = yypacc[yystate];
if (yyn == YYFLAG)
goto yydefault;
/* No se puede => leer un lookahead si an no lo tenemos. */
/* yychar es YYEMPTY, YYEOF o un token vlido en forma externa. */
if (yychar == YYEMPTY)
{
#ifdef YYDEBUG
fprintf(stderr, "Leer un token: ");
#endif
yychar = YYLEX;
}
/* Poner en yychar1 el cdigo interno del token para utilizarlo */
Anlisis Sintctico en Procesadores de Lenguaje
160
/* como ndice en las tablas */
if (yychar <= 0) /* Fin de la entrada. */
{
yychar1 = 0;
yychar = YYEOF;

#ifdef YYDEBUG
fprintf(stderr, "Final de la entrada.\n");
#endif
}
else
{
yychar1 = YYTRADUCE(yychar);
#ifdef YYDEBUG
fprintf(stderr, "El siguiente token es %d (%s)\n", yychar,
yytnom[yychar1]);
#endif
}
yyn += yychar1;
if (yyn < 0 || yyn > YYLAST || yycheq[yyn] != yychar1)
goto yydefault;
yyn = yytabla[yyn];
/* yyn indica qu hacer para este token en este estado. */
/* Negativo => reducir por la regla -yyn */
/* Positivo => cargar y pasar al estado yyn */
/* 0, o mnimo nmero negativo => error. */
if (yyn < 0)
{
if (yyn == YYFLAG)
goto yyerrlab;
yyn = -yyn;
goto yyreduce;
}
else if (yyn == 0)
goto yyerrlab;
if (yyn == YYFINAL)
YYACCEPT;
/* Cargar el token lookahead. */
#ifdef YYDEBUG
fprintf(stderr, "Cargar el token %d (%s), ", yychar,
yytnom[yychar1]);
Anlisis Sintctico en Procesadores de Lenguaje
161
#endif
/* Deshacerse del token que ha sido cargado a menos que sea eof. */
if (yychar != YYEOF)
yychar = YYEMPTY;

*++yyvsp = yylval;
#ifdef YYLSP_NEEDED
*++yylsp = yylloc;
#endif
/* Contar los tokens que han sido cargados desde el ltimo error. */
if (yyerrstatus) yyerrstatus--;
yystate = yyn;
goto yynewstate;
/* Ejecutar la accin por defecto para este estado. */
yydefault:
yyn = yydefacc[yystate];
if (yyn == 0)
goto yyerrlab;
/* Reducir por la regla yyn. */
yyreduce:
yylen = yyr2[yyn];
yyval = yyvsp[1-yylen];
#ifdef YYDEBUG
if (yylen == 1)
fprintf (stderr, "Reducir 1 valor via linea %d, ",
yyrlinea[yyn]);
else
fprintf (stderr, "Reducir %d valores via linea %d, ",
yylen, yyrlinea[yyn]);
#endif
/* Copiar aqu el fichero de acciones sustituyendo a '$'. */
switch (yyn) {
case 4:
#line 19 "calculad.y"
{ printf("\t%.10g\n", yyvsp[-1]); break;}
case 5:
#line 20 "calculad.y"
{ yyerrok; break;}
case 6:
#line 23 "calculad.y"
{ yyval = yyvsp[0]; break;}
Anlisis Sintctico en Procesadores de Lenguaje
162
case 7:
#line 24 "calculad.y"
{ yyval = yyvsp[-2] + yyvsp[0]; break;}
case 8:
#line 25 "calculad.y"
{ yyval = yyvsp[-2] - yyvsp[0]; break;}
case 9:
#line 26 "calculad.y"
{ yyval = yyvsp[-2] * yyvsp[0]; break;}
case 10:
#line 27 "calculad.y"
{ yyval = yyvsp[-2] / yyvsp[0]; break;}
case 11:
#line 28 "calculad.y"
{ yyval = -yyvsp[0]; break;}
case 12:
#line 29 "calculad.y"
{ yyval = pow (yyvsp[-2], yyvsp[0]); break;}
case 13:
#line 30 "calculad.y"
{ yyval = yyvsp[-1]; break;}
}
#line 308 "simple.prs"
yyvsp -= yylen;
yyssp -= yylen;
#ifdef YYLSP_NEEDED
yylsp -= yylen;
#endif
#ifdef YYDEBUG
short *ssp1 = yyss - 1;
fprintf (stderr, "pila de estados actual");
while (ssp1 != yyssp)
fprintf (stderr, " %d", *++ssp1);
fprintf (stderr, "\n");
#endif
*++yyvsp = yyval;
#ifdef YYLSP_NEEDED
yylsp++;
if (yylen == 0)
{
yylsp->first_line = yylloc.first_line;
Anlisis Sintctico en Procesadores de Lenguaje
163
yylsp->first_column = yylloc.first_column;
yylsp->last_line = (yylsp-1)->last_line;
yylsp->last_column = (yylsp-1)->last_column;
yylsp->text = 0;
}
else
{
yylsp->last_line = (yylsp+yylen-1)->last_line;
yylsp->last_column = (yylsp+yylen-1)->last_column;
}
#endif
/* Ahora "cargar" el resultado de la reduccin. Determinar a qu */
/* estado se pasa a continuacin */
yyn = yyr1[yyn];
yystate = yypgoto[yyn - YYNTBASE] + *yyssp;
if (yystate >= 0 && yystate <= YYLAST && yycheq[yystate] == *yyssp)
yystate = yytabla[yystate];
else
yystate = yydefgoto[yyn - YYNTBASE];
goto yynewstate;
yyerrlab: /* Detectar errores */
if (! yyerrstatus)
/* Informar de que se ha producido un error si no nos estamos */
/* recuperando de otro */
{
++yynerr;
yyerror("error de anlisis");
}
if (yyerrstatus == 3)
{
/* Si hemos intentado volver a utilizar el lookahead despus */
/* del error y no hemos podido, saltarlo */
if (yychar == YYEOF)
YYABORT;
#ifdef YYDEBUG
fprintf(stderr, "Desechar token %d (%s).\n", yychar,
yytname[yychar1]);
#endif
yychar = YYEMPTY;
}
/* Si no, intentar volver a utilizar el lookahead despus de cargar*/
Anlisis Sintctico en Procesadores de Lenguaje
164
/* el token error */
yyerrstatus = 3;
goto yyerrhandle;
yyerrpop:
if (yyssp == yyss) YYABORT;
yyvsp--;
yystate = *--yyssp;
#ifdef YYLSP_NEEDED
yylsp--;
#endif
#ifdef YYDEBUG
short *ssp1 = yyss - 1;
fprintf (stderr, "Error: pila de estados actual");
while (ssp1 != yyssp)
fprintf (stderr, " %d", *++ssp1);
fprintf (stderr, "\n");
}
#endif
yyerrhandle:
yyn = yypacc[yystate];
yyn += YYTERROR;
yyn = yytabla[yyn];
if (yyn < 0)
{
if (yyn == YYFLAG)
goto yyerrpop;
yyn = -yyn;
goto yyreduce;
}
else if (yyn == 0)
goto yyerrpop;
if (yyn == YYFINAL)
YYACCEPT;
#ifdef YYDEBUG
fprintf(stderr, "Cargar el token error, ");
#endif
*++yyvsp = yylval;
#ifdef YYLSP_NEEDED
*++yylsp = yylloc;
#endif
yystate = yyn;
Anlisis Sintctico en Procesadores de Lenguaje
165
goto yynewstate;
}
#line 33 "calculad.y"
/* cdigo c adicional */
/* analizador lxico */
#include <ctype.h>

yylex ()
{
int c;
while ((c = getchar()) == ' ' || c == '\t') ;
if (c == '.' || isdigit (c))
{
ungetc(c, stdin);
scanf ("%lf", &yylval);
return NUM;
}
if (c == EOF) return 0;
return c;
}
/* principal */
main()
{
yyparse();
}
/* subrutina de error */
#include <stdio.h>
yyerror (s)
char *s;
{
printf ("%s\n", s);
}

Anlisis Sintctico en Procesadores de Lenguaje
166

EJERCICIOS PROPUESTOS
Ejercicio 1
Sea la gramtica cuyo smbolo inicial es <A> y las reglas de produccin son las
siguientes:
<A> ::= x | ( <B> )
<B> ::= <A> <B>
<C> ::= { + <A> }
a) Construir un autmata de pila que reconozca el lenguaje generado por dicha
gramtica.
b) El autmata es determinista?
c) La gramtica es ambigua?
d) Es LL(1)?
e) Cul es el lenguaje reconocido por la gramtica?
Ejercicio 2
Sea la gramtica: S SS+|SS*|a
a) Es ambigua? En el caso de que la respuesta sea afirmativa demostrarlo. En el
caso de que sea negativa justificarlo lo mejor posible.
b) Indicar de alguna forma el lenguaje reconocido por la gramtica.
c) Es LL(1)? Si no lo es indicar las razones y escribir si es posible una gramtica
equivalente LL(1)
d) Construir un autmata que reconozca el lenguaje.
e) Es posible construir un autmata determinista para reconocer este lenguaje?
En caso afirmativo construirlo.
f) Escribir una entrada al yacc que dada una cadena de lenguaje generado por la
gramtica dada nos indique si pertenece o no al lenguaje.
Ejercicio3
Sea la gramtica S xSyS | ySxS |
a) Es ambigua ? Por qu ?
Anlisis Sintctico en Procesadores de Lenguaje
167
b) Qu lenguaje genera la gramtica ?
c) La cadena xyxy pertenece al lenguaje. Determinar su rbol sintctico.
Ejercicio 4
Sea la gramtica:
S x | ( S R
R , S R | )
a) Determinar los conjuntos de smbolos INICIALES, SIGUIENTES y
DIRECTORES.
b) A partir de los conjuntos anteriores comprobar si es LL(1).
Ejercicio 5
Sea la gramtica:
A d A | d B | f
B g
a) Determinar los conjuntos de smbolos INICIALES, SIGUIENTES y
DIRECTORES.
b) A partir de los conjuntos anteriores comprobar si es LL(1).
Ejercicio 6
Sea la gramtica:
S X d
X C
X B a
C
B d
a) Determinar los conjuntos de smbolos INICIALES, SIGUIENTES y
DIRECTORES.
b) A partir de los conjuntos anteriores comprobar si es LL(1).
Ejercicio 7
Sea la gramtica:
A B x y | x
B C D
C A | C
Anlisis Sintctico en Procesadores de Lenguaje
168
D d
a) Es recursiva a izquierdas de forma directa o indirecta?
b) En caso afirmativo determinar una gramtica equivalente que no sea recursiva
a izquierdas.
c) Si es posible obtener una gramtica equivalente LL(1) y comprobarlo.
Ejercicio 8
Escribir un intrprete de MUSIM/1 con lex y yacc.
Ejercicio 9
Construir las tablas de precedencia y asociatividad de los operadores en los lenguajes
FORTRAN, Pascal y C.
Ejercicio 10
Construir una gramtica LL(1) para las expresiones de los lenguajes FORTRAN,
Pascal y C.
Ejercicio.11
Disear una gramtica LL(1) para el lenguaje Pascal estndar completo.
Ejercicio.12
Construir una entrada al lex y yacc para reconocer el lenguaje C ANSI completo.
Anlisis Sintctico en Procesadores de Lenguaje
169
BIBLIOGRAFIA

AHO86 Aho A.V. , R. Sethi and J.D. Ullman. Compilers: Principles, techniques, and
tools. Addison-Wesley, 1986. Versin castellana: Compiladores: Principios,
tcnicas y herramientas. Addison-Wesley Iberoamericana, 1990.
ALVA91 Alvarez Rojo, A. R. Traductor de Pascal a C. Proyecto Fin de carrera. Escuela
Universitaria de Informtica de Oviedo, Universidad de Oviedo, 1991
BAUE74 Bauer F.L., Eickel J. Compiler construction: An Advanced Course.
Lecture Notes in Computer Science 21. Springer-Verlag, 1974.
BENN90 Bennett, J. P. Introduction to compiling techniques. A first course using ANSI C,
LEX and YACC. McGraw-Hill, 1990.
CABA91 Cabal Montes E. y Cueva Lovelle, J.M. Generador de Analizadores Sintcticos:
YACCOV. Cuaderno Didctico N45, Dto. de Matemticas, Universidad de
Oviedo, 1991
CUEV91 Cueva Lovelle, J.M. Lenguajes, Gramticas y Autmatas. Cuaderno
Didctico N36, Dto. de Matemticas, Universidad de Oviedo, 1991.
CUEV93 Cueva Lovelle, J.M. Anlisis lxico en procesadores de lenguaje. Cuaderno
Didctico N48, Dto. de Matemticas, Universidad de Oviedo, 2 Edicin 1993.
CUEV93b Cueva Lovelle, J. M. y M P. A. Garca Fuente. Programacin en FORTRAN.
Cuaderno Didctico N 67, Dto. de Matemticas, Universidad de Oviedo, 1993.
CUEV94 Cueva Lovelle J. M., M P. A. Garca Fuente, B. Lpez Prez, M C. Luengo
Dez, y M. Alonso Requejo. Introduccin a la programacin estructurada y
orientada a objetos con Pascal. Cuaderno Didctico N 69, Dto. de Matemticas,
Universidad de Oviedo, 1994.
CUEV94b Cueva Lovelle, J.M. Conceptos bsicos de Traductores, Compiladores e
Intrpretes. Cuaderno Didctico N9, Dto.de Matemticas, Universidad de
Oviedo, 4 Edicin 1994.
EAR70 J. Earley. An efficient context-free parsing algorithm. Communications of the
ACM, 13(2):94-102, 1970.
ELLI90 Ellis M.A. y Stroustrup B. The annotated C++ reference manual. ANSI base
document. Addison-Wesley, 1990. Versin en Castellano: C++ manual de
referencia con anotaciones. Addison-Wesley/ Daz de Santos, 1994.
GARM91 Garmn Salvador M M., J.M. Cueva Lovelle, y Salgueiro Vzquez J.C. Diseo
y construccin de un compilador de C (Versin 2.0). Cuaderno Didctico N46
Dto. de Matemticas. Universidad de Oviedo, 1991.
HOLU90 Holub A.I. Compiler design in C. Prentice-Hall, 1990.
HOPC79 Hopcroft J. E. y Ullman J.D. Introduction to automata theory, languages and
computation. Addison-Wesley, 1979.
Anlisis Sintctico en Procesadores de Lenguaje
170
INST01 Magelang Institute. ANTLR 2.x.x, 2001. URL: http://www.antlr.org
JAVA01 Java Compiler Compiler (JavaCC) - The Java Parser
Generatorhttp://www.webgain.com/products/java_cc/documentation.html, Septiembre
2001
JOHN75 Johnson S.C. Yacc-yet another compiler compiler. UNIX Programmer's.
Manual 2. AT&T Bell Laboratories, 1975.
KATR94 Katrib Mora, M. Programacin orientada a objetos en C++. Infosys,
Mxico, 1994.
KATR94b Katrib Mora, M. Programacin orientada a objetos a travs de C++ y Eiffel.
Quinta Escuela Internacional de invierno en temas selectos de computacin,
Zacatecas, Mxico, 1994.
KERN84 Kernighan B.W. y Pike R. The UNIX programming environment. Prentice Hall,
1984. Versin en castellano: El entorno de programacin UNIX, Prentice-Hall
Hispanoamericana, 1987.
KERN88 Kernighan B.W. y D.M. Ritchie. The C programming language. Second Edition
Prentice-Hall, 1988. Versin en castellano: El lenguaje de programacin C.
Segunda edicin. Prentice-Hall, 1991.
LEIV93 Leiva Vzquez J.A. y Cueva Lovelle J.M. Construccin de un traductor de
lenguaje Eiffel a C++. Cuaderno didctico N 76. Departamento de Matem ticas.
Universidad de Oviedo (1993).
LESK75 Lesk M.E. y Schmidth E. Lex-a lexical analyzer generator. UNIX Programmer's
Manual 2. AT&T Bell Laboratories, 1975.
LEVI92 Levine J. R., T. Mason, Brown D. lex & yacc. UNIX programming tools.
O'Reilly & Associates (1992).
MART93 Martnez Garca, J.M. y Cueva Lovelle, J.M. Generador de analizadores lxicos:
GALEX. Cuaderno Didctico N 66. Dto. de Matemticas, Universidad de
Oviedo, 1993.
MEYE88 Meyer B. Object-oriented Software Construction. Prentice-Hall 1988.
MEYE92 Meyer B. Eiffel. The language. Prentice-Hall 1992.
PARR97 Terence J. Parr. Language Translation Using PCCTS & C++. Autmata
Publishing Company, 1997. ISBN: 0962748854URL:
http://www.antlr.org/papers/pcctsbk.pdf, 2001
PYST88 Pyster A. B. Compiler design and construction (with C, Pascal and UNIX tools).
Second Edition. Ed. Van Nostrand Reinhold, 1988.
SANC86 Sanchs Llorca, F.J. y C. Galn Pascual Compiladores: Teora y construccin.
Ed. Paraninfo, 1986.
SANC89 Sanchez Dueas, G. y J.A. Valverde Andreu. Compiladores e intrpretes. Un
enfoque pragmtico. Ed. Diaz de Santos, 2 edicin, 1989.
SCHR85 Schreiner T. A. and Friedman H.G. Jr. Introduction to compiler construction
ith UNIX P ti H ll 1985
Anlisis Sintctico en Procesadores de Lenguaje
171
with UNIX. Prentice-Hall, 1985.
STRO86 Stroustrup B. The C++ programming language. Addison-Wesley 1986.
STRO91 Stroustrup B. The C++ programming language. Second Edition. Addison-
Wesley 1991. Versin en castellano: El lenguaje de programacin C++,
segunda edicin, Addison-Wesley/Daz de Santos, 1993.
STRO94 Stroustrup B. The Design and Evolution of C++. Addison-Wesley 1994.
TREM85 Tremblay J. P. and P.G. Sorenson. The theory and practice of compiler writing.
Ed. McGraw-Hill, 1985.
WAIT85 Waite M. W. and Goos G. Compiler construction. Springer-Verlag, second
edition 1985.
WATT91 Watt D.A. Programming Language Syntax and Semantics. Prentice-Hall, 1991.
WIRT76 Wirth, N. Algorithms+data structures= programs, Prentice-Hall, 1976. Versin
Castellana Algoritmos+estructuras de datos= programas. Ed. del Castillo, 1980.
YOU67 D. H. Younger, Recognition and Parsing of Context-Free Languages in Time n
3
,
Information and Control, Vol. 10, N 2, Febrero 1967, Pg.189-208.

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