Você está na página 1de 111

CASA ABIERTA AL TIEMPO

DIVISION DE CIENCIAS BASICAS E I NGENI ERI A


LIC E N CIA1 U RA E N C OM P UTACIO N
144172
PROYECTO DE INVESTIGACION
A U I A PARA REALKAR COMPILADORES
ALUMNO : AGUILAR CORNEJO MANUEL.
7
ASESOR : M. EN C. SERGiO PREZ RODEA.
ABRIL DE 1993.
INDICE
INDICE .....................................................
1
CAPITULO I . BOSQUEJO GENERAL ............................... 2
1.1 introduccin ........................................... 2
1.2 Las fases de un compilador ............................. 4
1.2.1 Anlisis lxico .................................. 5
1.2.2 Anlisis sintctico .............................. 5
1.2.3 Anlisis semntica ............................... 6
1.2.4 Administracin de la tabla de smbolos ........... 7
1.2.5 Deteccin e informacin de errores ............... 7
1.2.6 Generacin de cdigo intermedio .................. 8
1.2.7 Optimizacin de cdigo ........................... 9
1.2.8 Generacin de cdigo ............................. 9
CAPITULO I1 . ANALISIS LEXICO ............................... 12
2.1 Expresiones regulares .................................. 14
2.2 Definiciones regulares ................................. 18
2.3 Autmatas .............................................. 19
2.4 LEX (Generador de analizadores lxicos) ................ 28
2.4.1 Algunos ejemplos de especificacin para LEX ...... 31
2.4.2 Ejemplo de un analizador lxico .................. 36
CAPITULO I11 . ANALISIS SINTACTICO ......................... 3 8
3.1 Gramticas independientes del contexto ................. 38
3.2 Anlisis sintctico descendente ........................ 42
3.2.1 Gramticas LL(1) ................................. 48
3.2.1.1 Clculo del FIRST ....................... -50
3.2.1.2 Otras caractersticas de las
gramticas LL(1) ......................... 54
3.2.1.3 Clculo del FOLLOW ....................... 55
3.3 Anlisis sintctico ascendente ......................... 57
3.3.1 Implementacin por medio de una pila de anlisis
sintctico por desplazamiento y reduccin ........ 61
3.3.2 Conflictos durante el anlisis sintctico ........ 62
3.3.3 Algoritmo de anlisis sintctico LR(1) ........... 64
3.3.4 Construccin de tablas de anlisis sintctico .... 65
3.3.4.1 La operacin cerradura ................... 66
3.3.4.2 La operacin ir-a ........................ 68
3.3.4.3 Tablas de anlisis sintctico LR ......... 70
3.4 YACC (Generador de analizadores sintcticos) ........... 72
3.4.1 Especificaciones bsicas ......................... 73
3.4.2 Acciones ......................................... 75
3.4.3 Ejemplos de una gramtica completa ............... 78
1
. .......... ............... . . . . . . - * L__- .........
- , l l - . l l l l l l
5.1 Lenguaje ensamblador para el 8088-8086 ................. 93
5.2 Generando cdigo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . g 8
5.3 Ejemplo de un compilador completo . . . . . . . . . . . . . . . . . . . . . . 99
I#TRODCCIO#
A grandes rasgos, un compilador es un programa que lee un
programa escrito en un lenguaje fuente (lenguaje expresivo de
alto nivel), y lo traduce a un programa equivalente en otro
lenguaje, el lenguaje objeto (vease figura 1.1) . Como parte
importante de este proceso de traduccin, el compilador informa a
su usuario de la presencia de errores en el programa fuente.
Programa Objeto
(Lenguaje de bajo
nivel) .
Programa Fuente
(Lenguaje expresivo ---
de alto nivel).
Fig. 1.1 Funcin de un compilador.
Los compiladores a menudo se clasifican como de una pasada, de
multiples pasadas, de carga y ejecucin, de depuracin o de
optimizacin, dependiendo de como hyan sido construidos o de que
funcin depende que realizan. A pesar de esta aparente
complejidad, las tareas bsicas que debe realizar cualquier
compilador son escencialmente las mismas. Al comprender tales
tareas, se pueden construir compiladores de gran diversidad de
lenguaje fuente y mquina objeto tilizando las mismas tcnicas
bsicas.
Nuestro conocimiento sobre como organizar y escribir
compiladores ha aumentado mucho desde que comenzaron a aparecer
los primeros compiladores a pricipios de los aos cincuenta. Es
dificil dar una fecha exacta de la aparicin de el primer
compilador, Por que en un principio gran parte de el trabajo de
experimentacin y aplicacin se realiz de manera independiente
por varios grupos. Gran parte de los primeros trabajos de
compilacin estaba relacionada con la traduccin de formulas
aritmticas a cdigo mquina.
En la decada de 1950, se consider a los compiladores como
programas notablemente difciles de escribir. El primer
compilador de FORTRAN, por ejemplo, necesit para su
implementacin 18 aos de trabajo en grupo (Backus y otros
[1975]). Desde entonces, se han descubierto tcnicas sistemticas
para manejar muchas de las importantes tareas que surgen en la
compilacin. Tambin se han desarrollado buenos lenguajes de
implementacin, entornos de programacin y herramientas de
software (como generadores de analizadores lxicos y
sintcticos). Con estos avances, puede hacerse un compilador real
incluso como proyecto de estudio, en un curso sobre diseo de
compiladores.
3
En la compilacin hay .dos partes: anlisis y sntesis. La
parte del anlisis divide al programa fuente en sus elementos
componentes y crea una representacin intermedia del programa
fuente. La parte de la sntesis construye el programa objeto
deseado a partir de la representacin intermedia. De las dos
partes, la sntesis es la que requiere las tecnicas ms
especializadas. Se examinar el anlisis y se esbozar la forma
de sintetizar el cdigo objeto en un compilador estndar.
\
I
1.2 LAS FASES DE UN COMPILADOR.
Optimizador de .
/
Cdigo Intermedio
Conceptualmente un compilador opera en fases, cada una de las
cuales transforma el programa fuente de una representacin en
otra. En la figura 1.2 se muestra una descomposicin tpica de
un compilador. En la prctica, se pueden agrupar algunas fases, y
las representaciones intermedias entre las fases agrupadas no
necesitan ser construidas explcitamente.
Programa Fuente
I
V
Analizador Lxico 1
I
V
Administrador L Analizador Semantic0 Manej ador
de la tabla de
Errores
\t-------j
de smbolos I
I V
r
!
4
Las tres primeras fases, forman la mayor parte de la porcin
de anlisis de un compilador. Otras actividades, la adquisicin
de la tabla de smbolos y el manejo de errores, se muestran en
interaccin con las seis fases de anlisis lxico, anlisis
sintctico, anlisis semntico, generacin de cdigo intermedio,
optimizacin de cdigo y generacon de cdigo. De modo informal
tambin se llamarn nfasesn al administrador de la tabla de
smbolos y al manejador de errores.
1.2.1 Anlisis Lxico.
Anlisis Lineal: anlisis de cadenas de caracteres que
constituyen el programa fuente leyndose de izquierda a derecha y
agrupndose en componentes lxicos, que son secuencias de
caracteres que tienen un significado colectivo.
En un compilador, el anlisis lineal se llama anlisis lxico
o exploracin. Por ejemplo, en el anlisis lxico los caracteres
de la proposicin de asignacin
posicin := inicial +velocidad * 60
se agruparan en los componentes lxicos siguientes:
1.- El identificador posicin.
2.- El smbolo de asignacin :=.
3.- El identificador inicial.
4.- El signo de suma.
5.- El identificador velocidad.
6.- El signo de multiplicacin.
7 . - El nilmero 60.
Los espacios en blanco que separan los caracteres de estos
componentes lxicos normalmente se eliminan durante el anlisis
lxico .
1.2.2 Anlisis Sintctico.
Anlisis sintctico implica agrupar los componentes lxicos de
el programa fuente en frases gramaticales que el compilador
utiliza para sintetizar la salida. Por lo general, las frases
gramaticales del programa fuente se representan mediante un rbol
de anlisis sintctico como el que se ilustra en la figura 1.3.
En la expresin I) inicial + velocidad * 60 I(, la frase
velocidad * 60 es una unidad lgica, por que las convenciones
usuales de las expresiones aritmticas indican que la
multiplicacin se hace antes que la suma. Puesto que la expresin
inicial +velocidad va seguida de un *, no se agrupa en una
s1a frase independiente en la figura 1.3
5
proposicin
de asignacin
I
1 i
I
:=
identificador expresin
pos 1 cin +
1 \
expresin expresin
I I
7
expresin expresin
I
I I
I
*
identificador
inicial
nmero identificador
velocidad
I
velocidad
Fig 1.3 Arbol de anlisis sintctico para una expresin
La estructura jerrquica de un programa normalmente se expresa
utilizando reglas recursivas. Por ejemplo se pueden dar las
siguientes reglas como parte de la definicin de expresiones:
1.- Cualquier identificador es una expresin.
2.- Cualquier nmero es una expresin.
3.- Si expresin1 y expresin2 son expresiones entonces tambin
lo son:
expresin1 +expresin2
expresin1 * expresin2
( expresin ).
La divisin entre anlisis lxico y anlisis sintctico es
algo arbitraria. Generalmente se elige una divisin que
simplifique la tarea completa de el anlisis.
1.2.3 Anlisis Semntico.
La fase de anlisis semntico revisa el programa fuente para
tratar de encontrar errores semnticos y rene la informacin
sobre los tipos para la fase posterior de generacin de cdigo.
En ella se utiliza la estructura jerrquica determinada por la
fase de anlisis sintctico para identificar los operadores y
operandos de expresiones y proposiciones.
Un componente importante de el anlisis semntico es la
verificacin de tipos. Aqu el compilador verifica si cada
6
operador tiene operandos permitidos por la especificacin de el
lenguaje fuente. Por ejemplo, las definiciones de muchos
lenguajes de programacin requieren que el compilador indique un
error cada vez que se use un nmero real como indice de una
matriz .
1.2.4 Administracin de la Tabla de Smbolos.
Una funcin escencial de un compilador es registrar los
identificadores utilizados en el programa fuente y reunir
informacin sobre los distintos atributos de cada identificador.
Estos atributos pueden proporcionar informacin sobre la memoria
asignada a un identificador, su tipo, su dunbito (la parte del
programa donde tiene validez) y, en el caso de nombres de
procedimientos, cosas como el nmero y tipos de sus argumentos,
el mtodo de pasar cada argumento (por ejemplo, por referencia) y
el tipo que devuelve, si los hay.
Una tabla de smbolos es una estructura de datos que contiene
un registro por cada identificador, con los campos para los
atributos del identificador. La estructura de datos permite
encontrar rpidamente el registro de cada identificador y
almacenar o consultar rpidamente datos de ese registro.
Cuando el analizador lxico detecta un identificador en el
programa fuente, el identificador se introduce en la tabla de
smbolos. Sin embargo, normalmente los atributos de un
identificador no se pueden determinar durante el anlisis lxico.
Por ejemplo, en una declaracin en Pascal como
var posicin, inicial, velocidad : real:
el tipo real no se conoce cuando el analizador lxico encuentra
posicin, inicial, y velocidad.
Las fases restantes introducen informacin sobre los
identificadores en la tabla de smbolos y despus la utilizan de
varias formas. Por ejemplo, cuando se est haciendo el anlisis
semntico y la generacin de cdigo intermedio, se necesita saber
cules son los tipos de los identificadores, para poder comprobar
si el programa fuente los usa de una forma vlida y as poder
generar las operaciones apropiadas con ellos. El generador de
cdigo, por lo general, introduce y utiliza informacin
detallada sobre la memoria asignada a los identificadores.
1.2.5 Deteccin e Informacin de Errores.
Cada fase puede encontrar errores. Sin embargo, despus de
detectar un error, cada fase debe de tratar de alguna forma ese
error, para poder continuar la compilacin, permitiendo la
deteccin de ms errores en el programa fuente. Un compilador que
7
.-I.__" --- .- ... ... . . ... ~- I . . . ... . . ...
se detiene cuando encuentra el primer error, no resulta tan til
como debiera.
Las fases de anlisis sintctico y semntico por lo general
manejan una gran porcin de los errores detectables por el
compilador. La fase lxica puede detectar errores donde los
caracteres restantes de la entrada no forman ningtln componente
lxico del lenguaje. Los errores donde la cadena de componentes
lxicos violan las reglas de estructura (sintaxis) del lenguaje
son determinados por la fase de anlisis sintctico. uarante el
anlisis semntico el compilador intenta detectar construcciones
que tengan la estructura sintctica correcta, pero que no tengan
signifcado para la operacin implicada, por ejemplo, si se
intenta sumar dos identificadores, uno de los cuales es el nombre
de una matriz, y el otro, el nombre de un procedimiento.
1.2.6 Generacin de Cdigo intermedio
Despus de los anlisis sintctico y semntico, algunos
compiladores generan alguna representacin intermedia explcita
del programa fuente. Se puede considerar esta representacin
intermedia como un programa para una mquina abstracta. Esta
representacin intermedia debe tener dos propiedades importantes;
debe ser fcil de producir y fcil de traducir al programa
objeto .
La representacin intermedia puede tener diversas formas. En
cpitulos posteriores se trata una forma intermedia llamda
lecdigo de tres direcciones", que es como el lenguaje ensamblador
para una mquina. El cdigo tres direcciones consiste en una
secuencia de instrucciones, cada una de las cuales tiene como
mximo tres operandos. Veamos un ejemplo.
templ : =EntAReal(60)
temp2 := id3 * templ
temp3 := id2 +temp2
id1 := temp3
Esta representacin intermedia tiene varias propiedades.
Primera cada instruccin de tres direcciones tiene a lo sumo un
operador, adems de la asignacin. Por tanto, cuando se generan
esas instrucciones, el compilador tiene que decidir el orden en
que deben efectuarse las operaciones; la multiplicacin precede a
la adicin. Segunda, el cornpilador debe generar un nombre
temporal para guardar los valores calculados por cada
instruccin. Tercera, algunas instrucciones de "tres direcciones"
tienen menos de tres operandos, por ejemplo, la primera y la
ltima instruccin.
Posteriormente se tratarn las principales representaciones
intermedias empleadas en los compiladores. En general, estas
representaciones deben hacer algo ms que calcular expresiones;
tambin deben manejar construcciones de flujo de control y
llamadas a procedimientos.
8
1.2.7 Optimizacin de Cdigo.
La fase de optimizacin de cdigo trata de mejorar el cdigo
intermedio, de modo que resulte un cdigo de Mquina ms rpido
de ejecutar. Algunas optimizaciones sontriviales. Por ejemplo, un
algoritmo natural genera el cdigo intermedio (1.1) utilizando
una instruccin para cada operador de la representacin de rbol
despues del anlisis semntico, aunque hay una forma mejor de
realizar los mismos clculos utilizando las dos instrucciones
templ := id3 * 60.0
id1 := id2 +templ
Este sencillo algoritmo no tiene nada de malo, puesto que el
problema se puede solucionar en la fase de optimizacin de
cdigo. Esto es, el compilador puede deducir que la conversin de
60 de entero a real se puede hacer de una vez por todas en el
momento de la compilacin, de modo que la operacin EntAReal se
puede eliminar. Adems, temp3 se usa slo una vez, para
transmitir su valor a idl. Entonces resulta seguro sustituir id1
por temp3, a partir de lo cual la ltima proposicin de (1.1) no
se necesita y se obtiene el cdigo de (1.2).
Hay mucha variacin en la cantidad de optimizacin de cdigo
que ejecutan los distintos compiladores, una parte significativa
del tiempo del compilador se ocupa en esta fase. Sin embargo, hay
optimizaciones sencillas que mejoran senciblemente el tiempo de
ejecucin del programa objeto sin retardar demasiado la
compilacin.
1.2.8 Generacin de Cdigo.
La fase final de un compilador es la generacin de cdigo
objeto, que por lo general conciste en cdigo mquina
relocalizable o cdigo ensamblador. Las posiciones de memoria se
seleccionan para cada una de las variables usadas por el
programa. Despus cada una de las instrucciones intermedias se
traduce a una secuencia de instrucciones de mquina que ejecutan
la misma tarea. Un aspecto desicivo es la asignacin de variables
a registros.
Como ejemplo, si utilizamos los registros AX y BX en la
traduccin del cdigo (1.2) a lenguaje ensamblador para la
familia de procesadores 8086-8088 tendramos:
MOV AX,id3
MUL 60
MOV BX, id2
ADD AX,BX
MOV id1,AX
Este cdigo traslada el contenido de id3 a AX en la primera
instruccin, en la segunda multiplica Ax por 60, en la tercera
mueve el contenido de id2 a BX, en la cuarta suma Ax y BX (id2 +
tempi), y por ltimo en la quinta deposita el resultado de la
suma en la variable idl. Posteriormente se tratar con ms
detalle la generacin de cdigo. Como ejemplo final del capitulo
introductorio ilustraremos como una linea de cdigo fuente es
tratada a travs de cada fase de compilacin
posi ci n := i ni ci al +vel oci dad * 60
I
posi ci on 0 . .
i ni ci al . * *
vel oci dad . . .
I
I
Anal i zador l xi co
I
i d1 := i da' +i d3 * 60
:=
/------t
i d1 +
-
i d2 *
/ \
:=
rc------4
i d1 +
-
i d2 *
-
i d3 Ent AReal
I
I
60
1Gen. de cdi go I nt er , f
t empl : =EntAReal (60)
t emp2 := i d3 * t empl
t emp3 := i d2 +t emp2
i d1 := t emp3
I
I
t empl := i d3 * 60.0
i d1 := i d2 +t empl
I
Generador de cdi go
I
MOV AX, i d3
ML 60
MOV BX, i d2
ADD AX, BX
MOV i d1, AX Fi g. 1. 4 Traducci n de una proposi ci n.
11
CAPITUW 11. blAL1818 LEXICO.
El analizador lxico es la primera fase de un cornpilador. Sus
pricipales funciones son:
1.- Leer los caracteres de entrada del texto analizado y elaborar
como salida una secuencia de componentes lxicos (tokens).
2.- Eliminar los delimitadores del texto analizado, tal como,
espacios en blanco, caracteres de tabulador, saltos de linea
y comentarios de entre otros.
3.- Puesto que esta es la primera fase que lee el c6digo fuente a
analizar, deber de llevar una contabilidad sobre el nmero
de veces que se encontr el caracter de salto de linea, esto
con el fin de que cuando se presente una secuencia invalida
de caracteres sepamos en que linea ocurri.
4.- Detectar secuencias invalidas de caracteres, generando un
mensaje de error en la linea donde se encontr.
La funcin ms relevante de las mencionadas, es la primera,
consistente de leer los caracteres de entrada y elaborar como
salida una secuencia de componentes lxicos que utiliza el
analizador sintctico para hacer el anlisis. Esta interaccin,
esquematizada en la figura 2.1, suele aplicarse convirtiendo al
analizador lxico en una subrutina del analizador sintctico.
Recibida la orden "obten token" del analizador sintctico, el
analizador lxico lee los caracteres de entrada hasta que pueda
identificar el siguiente componente lxico, o bien, se encuentre
con una secuencia de caracteres invalida para lo cual mandar un
mensaje de error.
Programa
fuente
Token
Analizador
sintctico
obten el sig.
Tabla de
Smbolos
Fig. 2.1 Interaccin de un analizador lxico con un analizador
sintctico.
12
Componentes lxicos, patrones y lexemas.
COMPONENTE
LEXICO
Cuando se menciona el anlisis, ,los terminos, "componente
lxico" (token), npatrn", y "lexeman se emplea con significados
especificos. En la fig. 2.2 aparecen ejemplos de dichos usos. En
general, hay un conjunto de cadenas en la entrada para el cual se
produce como salida el mismo componente lbxico. Este conjunto de
cadenas se describe mediante una regla llamada patrn asiciado al
componente lxico. Se dice que el patrn concuerda con cada
cadena del conjunto. Un lexema es una secuencia de caracteres en
el programa fuente con la que concuerda el patrn para un
componente lxico. Por ejemplo, en la proposicin de pascal
LEXEMAS DE DESCRIPCION INFORMAL DEL
EJEMPLO PATRON
const pi =3.1416;
la subcadena pi es un lexema para el componente lxico
U ident if icador" .
const
if
relacin
id
nm
literal
const
if
pi, cuenta, D2
3.1416, O, 6.02
lWaca memoria"
const
if
< o <= o =o <> o >=o >
letra seguida de letras y dgitos
cualquier constante numrica
cualquier cosa entre Hn.
Fig. 2.2 Ejemplos de componentes lxicos.
Los componentes lxicos se tratan como smbolos terminales de
las gramticas del lenguaje fuente, con noares en negritas para
representarlos. Los lexemas para el componente lxico que
concuerdan con el patrn representan cadenas de carcateres en el
programa fuente que se pueden tratar juntos como una unidad
lxica .
Un patrn es una regla que describe el conjunto de lexemas que
pueden representar a un determinado componente lxico en los
programas fuentes. El patrn para el componente lxico const de
la figura 2.2 es simplemente la cadena sencilla const que
deletrea la palabra clave. El patrn para el componente lxico
relacin es el conjuntom de los seis operadores relacionales de
Pascal. Para describir con presicin los patrones para
componentes lxicos ms complejos, como id (para identificador) y
nm (para nmero), se utilizar la notacin de expresiones
regulares desarrolladas en parrafos posteriores.
13
El analizador lxico recoge informacin sobre los componentes
lxicos as como de sus atributos asociados. En la prdctica, los
componentes lxicos suelen tener un solo atributo -un apuntador
ala entrada de la tabla de smbolos donde se guarda la
informacin sobre el componente lxico; el apuntador se convierte
en el atributo del componente lxico. A efectos de diagnstico,
puede considerarse tanto el lexema para un identificador como el
nmero de linea en el que ste se encontr por primera vez. Estos
dos elementos de informacin se pueden almacenar en la entrada de
la tabla de smbolos para el identificador.
Errores Lxicos.
Son pocos los errores que se pueden detectar simplemente en el
nivel lxico porque un analizador lxico tiene una visin muy
restringida de un programa fuente. Si aparece por primera vez la
cadena fi en un programa en C en el contexto
fi ( a ==f(x) ) ...
Un analizador lxico no puede distinguir si fi es un error de
escritura de la palabra clave if o si es un identificador de
funcin no declarado. Como fi es un identificador vlido, el
analizador lxico debe devolver el componente lxico de un
identificador, y dejar que alguna otra fase del compilador se
encarge de estos errores.
2.1 EXPRESIONES REGULARES.
Definicin: Un alfabeto es un conjunto finito de smbolos.
Ejemplo:
s = { o, 1 }
S = { a, b, c )
ejemplos posibles de smbolos: el conjunto de letras, el conjunto
de nmeros, el conjunto de letras y de nmeros. Los cdigos ASCII
y EBCDIC son dos eejemplos de alfabetos de computador.
Definicin: Una cadena es cualquier secuencia finita de
smbolos de algn alfabeto.
Ejemplo: Sea el alfabeto S = { O, 1 ), entonces posibles
cadenas de este alfabeto seran:
x =
011; y =0000; z =1010; t =0101;
x =o; y =1; z =1111111100000000, etc.
14
I " ~ ~ .- .-I ...I I . .- . _. . ... .".- - - . . . ... -. - .-
La longuitud de una cadena "sn representada por Is, es el nmero
de smbolos que contiene del alfabeto del cual fuen formada. Por
ejemplo la cadena O11 tiene longuitud 3. La cadena vaca,
representada por E, es una cadena especial de longuitud cero.
Sea s una cadena, entonces definamos:
Prefijo de s: Una cadena que se obtiene eliminando cero o ms
smbolos desde la derecha de la cadena s i por
ejemplo ban es un prefijo de la cadena bandera.
subfijo de s : Una cadena que se forma suprimiendo cero o ms
smbolos desde la izquierda de una cadena s i por
ejemplo, era es subfijo de bandera.
subcadena de s: Una cadena que se obtiene suprimiendo un prefijo
y un subfijo de s i por ejemplo, ande es una
subcadena de bandera. Todo prefijo y subfijo de s
es una cadena de s, pero no toda subcadena de s
es un prefijo o subfijo de s. Para toda cadena s,
tanto s como E son prefijos, subfijos y
subcadenas de s.
prefijo, subfijo o subcadenas propios de s : Cualquier cadena no
vaca x que sea, respectivamente, un prefijo,
subfijo o subcadena de s tal que s <> x.
subsecuencia de s : Cualquier cadena formada mediante la
eliminacin de cero o ms smbolos no
necesariamente contiguos a s : por ejemplo, banda
es una subsecuencia de bandera.
Si x e y son cadenas, entonces la concatenacin de x e y, que
se denota por xy, es la cadena que resulta de agregar y a x. Por
ejemplo, si x =caza e y = fortunas, entonces la concatenacin
de xy =cazafortunas y yx = fortunascaza. La cadena vaca es el
elemento identidad para la operacin de concatenacin, esto es,
SE =ES =S .
Considerando la concatenacin como producto, cabra definir la
exponenciacin de cadenas como: SO sera E y para i > O
tendramos si = si-1s. Dado que Es = 8 , sl = s. Entonces s2 =
ss, s3 =sss, etc.
Definicin: Un lenguaje es cualquier conjunto de cadenas
formadas con los smbolos de algn alfabeto.
Ejemplo:
L = ( O , 1 } M = ( 00, 11 } N = ( E }
15
Sean L, D y M lenguajes como se definieron anteriormente.
Existen varias operaciones importantes definidas sobre los
lenguajes como :
1.-
2.-
3.-
4.-
Unin:
L U M = ( x l x E L & x E M )
L U M ={ O, 1, 00, 11 }
Concatenacin:
L U M = ( x i y l x E L & y E M }
L U M = ( 000, 001, 100, 111 }
L2 = ( 00, 01, 10, 11 }
L3 =L L L = ( 000, 001, 010, 011, 0 . . }
Cerradura de Kleene (concatenacin de cero o ms cadenas de
algn lenguaje L) .
L * = U L
9 i
i=O
Cerradura positiva
algn lenguaje)
L += u L
9 i
i=i
L+ =L1 u L2 u L3 u
...
01, 10, 11, ...}
(concatenacin de una o ms cadenas de
....
L+ =( o, 1, 00, 01, 10, 11, ...}
Definicin: Sea E un alfabeto. La expresin regular sobre E y los
conjuntos que ella denotan son definidos recursivamente como
sigue:
1.- 0 es una expresin regular y denota al
2.- E es una expresin regular y denota
3.- Para cada a E E, a es una expresin
conjunto vacio.
por el conjunto ( E }.
lenguaje compuesto del
al lenguaje compuesto
regular que denota al
}. (Aunque se usa la lenguaje compuesto por el conjnto { a
misma notacin para la tres , tcnicamente a expresin
regular a es distinta de la cadena a o del smbolo a. El
contexto aclarar a que trmino nos referimos).
4.- Si R y S son expresiones regulares denotando los lenguajes
L(R) y L(S) respectivamente, entonces, (rl s) , (rs) y r* son
expresiones regulares que denotan los lenguajes L(R) U L(S),
L(R)-L(S) y L(R) * respectivamente.
Se dice que un lenguaje designado por una expresin regular es
un conjunto regular. En la escritura de expresiones regulares se
pueden omitir algunos parentesis si tomamos en cuenta la
16
precedencia de los operadores. El * tiene mayor precedencia que
la concatenacin y que el I, y la concatenacin tienen mayor
precedencia que . Por ejemplo ( ( O (l*) ) + O ) puede ser
escrito como: 01*
Ej emplos :
Sea E ={ O, 1 ), el alfabeto.
Todos los nmeros divisible entre 10.
Los nmeros divisibles entre 10 son:
(10, 100, 110, 1000, lolo,..}
Por lo tanto tenemos la siguiente expresin
1 (O 11) *o
Todos los nmeros que comienzan con un 1.
El conjunto de los nmeros que comienzan con 1 es
{I, 10, 11, 101, 100, 110, ... }
Por lo tanto tenemos la siguiente expresin
Todos los nmeros impares
El conjunto de los nmeros impares es
{ 1, 11, 101, 111, 1001, 1011, . . o )
Por lo tanto tenemos la siguiente expresin
Todas las cadenas de ceros o unos que tengan al menos un cero.
(01 1) *O(Ol1) *
Todas las cadenas de ceros y unos cuyo segundo smbolo de
izquierda a derecha sea cero
Todas las cadenas de ceros y unos que no incluyan la subcadena
01.
Las cadenas que no incluye la subcadena O1 son
- 11111.. .
- 00000.. .
- 11100.. .
Esto es,
1*0*
Un reconocedor para un lenguaje L recibe como entrada una
cadena x y decide si sta pertence o no a L.
Si dos expresiones r y s representan al mismo lenguaje, se dice
que r y s son equivalentes y se escribe r =s. Por ejemplo, (alb)
= (bla).
2.2 DEFINICIONES REGULARES.
Si E es un alfabeto de smbolos bsicos, entonces una
definicin regulares una secuencia de la forma.
<nombre> ------ > <expresin regular>
ejemplo:
donde cada di es un nombre distinto, y cada ri es una expresin
regular sobre los smbolos de E U {dl,d2,..,dn)
Ejemplo: El conjunto de los identificadores de Pascal, es el
conjunto de cadenas de letras y dgitos que comienzan con una
letra. A continuacin se da una definicin regular para ese
conjunto .
letra -----> Al Bl Cl ... lZlalbl ... I z
dgito ----> 011121 ... 10
id ------- > letra (letra I dgito) *
Ejemplo: los nmeros sin signo en Pascal son cadenas, como
6657, 93.79, 33.96737, 9.813-4. La siguiente definicin regular
proporciona una especificacin presisa para esta clase de
cadenas .
dgito -----> 0111 ... 10
dgitos ---- > dgito.dgito*
fraccin-optativa ---- > .dgitosl E
exponente-optativo----> ( E (+ I - I E) dgitos) I E
numero ----> dgitos fraccin-optativa exponente-optativo
18
I_ ---
2.3 AUTOMATAS.
Un reconocedor de un leguaje es un programa que toma como
entrada una cadena x y responde %erdaderoN si x es una frase (o
token)del lenguaje, y llfalson si no lo es. Tal como se ilustra en
la siguiente figura
Este reconocedor de lenguajes ser utilizado por el analizador
sintctico para analizar la segunda fase de compilacin. Esto es,
el analizador sintctico pedir un token al reconocedor de
lenguajes (analizador lxico). Este leer el cdigo fuente de
entrada y si logra reconocer un token lo enviar al analizador
sintctico para que lo procese. En otro caso, mandar un mensaje
de error y procesar ste. El proceso continua de la misma manera
hasta que el cdigo es analizado totalmente. Tal como se ilustra
en la siguiente figura
Programa tabla de
donde: Rt es el reconocedor de tokens.
Ed es el reconocedor de delimitadores.
Se puede traducir una expresin regular en un reconocedor de
lenguajes construyendo un diagrama de transiciones generalizado
llamado "Autmata finito". Un autmata finito puede ser
B1deterministall o Itno deterrninistat1, donde no determinista
significa que en un estado se puede dar el caso de tener ms de
una transicin para el mismo smbolo de entrada.
Tanto los autmatas finitos deterministas como los no
deterministas pueden reconocer con precisin a los conjuntos
regulares. Por tanto, ambos pueden reconocer con con precisin lo
que denotan las expresiones regulares. Sin embargo, hay un
conflicto entre tiempo y espacio: mientras que un autmata finito
determinista puede dar reconocedores ms rpidos que un no
determinista, un autmata finito determinista puede ser mucho
mayor que un autmata no determinista equivalente.
DEFINICION. Un autmata finito determinista (DFA) es un modelo
matemtico formado por la quintupla (Q, E, s, qo, F), donde:
1) Q es un conjunto finito de estados.
2) E es el alfabeto de entrada.
3) s es una funcin de transicin Q X E ---> Q
4) qo E Q, es el estado inicial.
5) F C Q es el conjunto de estados finales.
NOTA: Los estados terminales se representan con doble circulo, y
los no terminales con circulo sencillo.
Ejemplo 1:
Por lo tanto tenemos el siguiente autmata
rb
DEFINICION. Se dice que un autmata acepta reconoce una cadena
x, si comenzando con el estado inicial, despus de seguir las
transiciones indicadas por los smbolos de la cadena x se llega a
un estado final.
Ejemplo 2: Tomemos como autmata, el mostrado en el ejemplo
anterior, entonces:
a) La cadena x=O1 es aceptada, ya que, comenzando en q0, al
leerel smbolo O tomamos la funcin de transicin s(q0,O) y
pasamos al estado O; a continuacin leemos un 1 y tomamos la
funcin de transicin s(q0,l) pasando al estado 1; puesto que ya
no hay ms smbolos en la cadena x, revizamos si en el
estadodonde estamos hubicados es terminal o no terminal; como el
estado es terminal, la cadena x=O1 es aceptada.
b) La cadena x=100 no es aceptada, pues, partiendo del estado
inicial q0 y siguiendo las funciones de transicin visitamos los
estados qlqOq0 terminando en q0; puesto que q0 no es un estado
final, entonces la cadena no es aceptada por este autmata (o no
20
pertenece al lenguaje reconocido por el autmata).
DEFINICION. El lenguaje reconocido por un autmataes el con junto
de cadenas que acepta.
Ejemplo 3: En el ejemplo 1 tenemos un autmata que acepta
cualquier cadena que termine con un 1, pues, si estamos en el
estado O y nos llega un 1 pasamos al estado uno, y si estamos en
el estado uno y nos llega un 1 nos quedamos en ese estado, el
cual es un estado terminal. En este caso particular el lenguaje
aceptado por ste autmata es el expresado por la expresin
regular (O I 1) *I
Ejemplo 4: De el
vemos que el lenguaje reconocido por ste, es el expresado por
x=l* o 1*
Ejemplo 5: Del siguiente autmata
vemos que genera el lenguaje x =o(oo)*~
EJERCICIOS.
Considerese el alfabeto E = { O, 1 }
1) Dibujar el DFA que reconozca el lenguaje { E }
21
2) Dibujar el DFA que reconozca el lenguaje ( O )
3) Dibujar el DFA que reconozca el lenguaje { O1 }
4) Dibujar el DFA que reconozca el lenguaje 1*, esto es, { E, 1,
11, 111, ... )
5) Dibujar el DFA que reconozca las cadenas de la forma xz donde
x consta de un nmero par de ceros y z de un nmero impar de
unos.
22
6) Dibujar el DFA que reconozca las cadenas de ceros y unos que
no tienen 2 ceros concecutivos.
Sea el alfabeto de entrada E = ( ASCII ) y las siguientes
definiciones regulares las que denotaran nuestro pequeo
lenguaje.
1 ----> albl ... lzlAlBl ... l Z
d ----> 1121 ... 1910
t ----> i(lJd)*
I si
I =
delimitadores ---> (blCRI(l*))*
donde: I denota la expresin OR
b denota caracteres en blanco.
CR denota el caracter de retorno de carro (enter)
1 denota cualquier letra
d denota cualquier dgito
t denota los tokens vlidos
23
El autmata generado por estas definiciones regulares y que
denota el mismo lenguaje es el siguiente
La forma en que el autmata es implementado y reconoce el
lenguaje, es la siguiente: se crea un buffer donde se almacenar
una linea de cdigo a analizar, se verificar si cada una de las
cadenas de sta linea es aceptada por el autmata; si es as se
regresar un token, tal como lo muestra la figura
Programa Analizador
fuente
Ejemplo:
Programa Fuente
si (comentario)
a=#b
Se lee la primer linea del cdigo fuente y se almacena en el
buffer
aPs aPs
V V
A
api
1) Se comienza con los apuntadores api y pas apuntando al inicio
del buffer. Se comienza por incrementar aps y che canos a que
estado pasamos al encontrar una 'sr (estados), puesto que es
entrada vlida incrementamos aps; leemos el siguiente caracter
'it, vemos si es entrada vlida, puesto que es as pasamos al
estado correspondiente (estado 3). Incrementamos aps leemos el
caracter apuntado por aps '(', puesto que no hay transicin para
ese caracter y puesto que estamos en un estado terminal, esta
cadena ya forma un token, el cual regresamos (al analizador
sintctico), pero antes incrementamos el apuntador api a donde
apunta aps, para tratar de encontrar el
siguiente token y ademas
retornamos al estado inicial (estado O).
2) mesto que api y aps apuntan a la localidad donde se encuentra
'(, pasamos al estado 5 y avanzamos aps. Leemos la palabra
'comentario' avanzando sucesivamente aps sin realizar cambio de
estado; hasta que aps apunta a ')' lo cual provoca que pasemos al
estado O. Esto es, ignoramos el comentario; despus de leer el
retorno de carro el cual nos deja en el mismo estado, cargamos
25
el buffer con la siguiente linea a analizar y se inicializan api
y aps a 1.
3) El buffer se cargo con
aPs
buffer [ * )
A
api
leemos el caracter apuntado por aps 'a', el cual provoca un
cambio de estado (estado 1) 8 avanzamos aps y leemos el caracter
apuntado por aps I = ' - # puesto que no hay transicin para ese
smbolo y puesto que estamos en un estado terminal (estado l),
enviamos un token (puesto que encontramos un identificador) al
analizador sintctico, incrementamos api a aps y pasamos al
estado O para comenzar el anlisis nuevamente.
Leemos el caracter apuntado por aps ' #' # el cual nos cambia al
estado 6, incrementamos aps; leemos el smbolo apuntado por aps
'b8 ; puesto que no hay transicin para ese smbolo y no estamos
en un estado final, entonces ocurri un error (smbolo o cadena
no vlida). La forma de manejar el error es: pasamos al estado
inicial (estado O), nos posicionamos en el siguiente caracter y
mandamos un mensaje de error.
La implementacin del autmata en un algoritmo lo mostraremos
a continuacin, slo que ademas de lo arriba sealado haremos un
manejo adicional con las cadenas reconocidas por el autmata. A
cada cadena vlida (no tomando en cuenta los comentarios) le
asignaremos un cdigo, y adems a los identificadores los
introduciremos en una tabla de smbolos con su cdigo y un
atributo adicional (su posicin en el cdigo fuente).
Cdigo Atributo
token l(l[d)* 1 posicin
2
3
declaramos api y aps globales
FUNCION LEX
Comienza
api <-- aps;
token <-- O;
REPITE
CASE bufferaps] DE
1-{s}: Comienza
aps <-- aps+l;
MIENTRAS buffer[aps] E (lld} HAZ
pas <-- aps+l;
26
t oken=l ;
ap- i d <-- manej a- i d(api , aps- l ) ;
CASE buf f er[aps] DE
Termi na;
8s8 : Comi enza
8i 8 : aps <-- aps+l ;
SI buf f er[aps] E {l i d) ENTONCES
Comi enza
Termi na;
aps <-- aps+l ;
got o edo 1;
OTRO
l l d- {8i 8) : Comi enza
Termi na;
t oken <-- 2;
aps =aps+l ;
got o dol ;
OTRO : Comi enza
t oken <-- 1;
ap- i d <-- manej a- i d(api , aps- l )
Termi na
FI N CASO
Termi na
8 = 8 : Comi enza
aps <-- aps+l ;
t oken <-- 3 ;
Termi na ;
' b8 : aps <-- aps+l ;
' CR8 : Comi enza
SI NOT(E0F) ENTONCES
Comi enza
Lee- Li nea (buf f er) ;
aps <-- 1;
Termi na ;
t oken <-- - 1;
OTRO
Termi na;
8(8 : Comi enza
aps <-- aps+l ;
CASE buf f erl aps] DE
1 : MI ENTRAS buf f er[aps] E { 1 ) HAZ
SI buf f er[aps] = 8)8 ENTONCES
OTRO ERROR(1);
aps <-- aps+l ;
aps <-- aps+l ;
8, : aps <-- aps+l ;
OTRO: ERROR(1);
FI N CASO
Termi na ;
OTRO : aps <-- aps+l ;
ERROR(2);
FI N CASO
HASTA t oken <> O;
LEX <-- t oken;
Termi na ;
I
2.4 LEX (Generador de Analizadores lxicos).
Lex es un generador de programas capaces de realizar el proce-
samiento lxico de archivos de texto, es decir programas que
pueden conocer ciertos patrones dentro del conjunto de caracteres
que forman parte de un archivo de texto y realizar manipulaciones
sobre tales cadenas. Los analizadores l6xiws son programas que
caen dentro de sta categora, pues su funcin consiste en descu-
brir las secuencias de caracteres que forman tokens o delimita-
dores vlidos de algn lenguaje fuente y realizan ciertas tareas
sobre tales cadenas: eliminan los delimitadores de la entrada y
asocian un cdigo nmerico a cada token.
Lex recibe como entrada un conjunto de expresiones regulares y
acciones proporcionadas por el usuario (que en adelante llamare-
mos Especificacin de entrada) y produce como salida un programa
escrito en lenguaje C llamado "yylex.cN. Cada expresin regular
tiene asociado un conjunto de acciones que deben ser realizadas
cada vez que una cadena con la forma indicada por la expresin
regular se a encontrada en la entrada (ver figura)
> YYLEX. C
Lxico
Especificacin ----
de entrada
> Salida
Entrada ---- -----
Fig. 2.4 Generacin de un analizador lxico utilizando el
compilador LEX.
El formato de la especificacin de entrada para lex consta de
tres partes:
declaraciones
%%
reglas de traduccin
%%
procedimientos auxiliares
donde las definiciones de funciones y funciones del usuario son
opcionales. El segundo %% es opcional, pero el primero se require
para marcar el inicio de las reglas. El programa ms pequeo que
puede escribirse en Lex, es:
0%
(Ni definiciones ni reglas), el cual genera un programa que
copia la entrada a la salida (pantalla por default), sin modifi-
cacin alguna, ya que de no indicarse ninguna otra,
la accin por
omisin del programa generado ser copiar los caracteres de
entrada a pantalla.
Una regla individual patra la especificacin de entrada podra
ser:
entero printf("Encontre la palabra ENTEROn);
cuya finalidad es buscar la palabra entero en la entreada y
escribir el mensaje "Encontre la palabra ENTEROn cada vez que
sta aparezca.
Si la accin asiciada a la expresin regular es una sla
instruccin de C, basta escribirla del lado derecho de la linea;
si se trata de una instruccin compuesta o emplea ms de una
lnea, la accin deber encerrarse entre llaves.
EXPRESIONES REGUIIARES.
Una expresin regular en la especificacin de Lex puede
contener dos tipos de caracateres: caracteres textuales (letras o
dgitos) y caracteres que denotan operadores ( "\[IA-? .
textual,
hay dos maneras de lograrlo:
* +I OS/ O%<' ) *
Si se desea emplear algn caracter operador en forma
1.- Entrecomillar el caracter operador. Por ejemplo:
xy 2 1) ++11
2.- Anteponiendo la diagonal invertida (\). Por ejemplo:
xYz\+\+
Ambas expresiones son equivalentes.
Las funciones desarrolladas por los caracteres operadores son:
- Denotar las clases de caracteres. El par de caracteres [ I
permite especificar una clase de caracteres. Por ejemplo, [abc]
representa a cualquiera de los caracteres a, b o c. Dentro de los
corchetes los operadores pierden su significado a exepcin de: \-
A .
- El caracter - indica rangos. Por ejemplo:
[a-z0-9<> - 3
indica la clase de caracteres que contiene las letras
minusculas, dgitos, los smbolos <, > y el caracter de
subrayado. Si se desea incluir el caracter - en una clase de
caracteres, este deber ser el primero o bien el ltimo. As
[ -+O-91
coincide con todos los dgitos y ambos signos.
- El operador indica complementacin. Si se desea incluir
dentro de alguna clase, ste deber ser el primer caracter dentro
de los corchetes, esto es
[ "abc]
representa todos los caracteres exepto a, b, y c, incluyendo
caracteres de control.
- Operador de caracter arbitrario
denota cualquier caracter, exepto el de nueva linea
- Operador opcional. El operador opcional 3 indica un elemento
opcional en la expresin. Por ejemplo:
ab?c
denota tanto ab como abc, ya que la b es opcional.
- Operadores de repeticin. La repeticin se indica mediante los
operadores +,*. Por ejemplo:
a*
representa las cadenas con cualquier nmero (cero o ms) de
a8s, incluyendo la cadena vaca.
a+
indica las cadenas con una o ms a's.
expresa todas las cadenas de letras minusculas; y
[A-Za-z][A-Za-zO-9]*
expresa todos los posibles identificadores de Pascal o C.
- Operadores de alternativas. El operador I indica alternativas.
Por e j emplo :
representa tanto la cadena ab como cd.
Los demas operadores se utilizan slo en ocaciones espordicas
por lo cual no sern tratados en el presente texto (si el lector
desea consultarlos puede hacerlo en el Manuel de Referencia de
Lex).
30
144172
Cabe sealar que Lex proporciona algunas variables que nos
pueden ser de mucha utilidad como:
- YYTEXT. Contiene la cadena que coincidi con alguna expresin
regular.
- YYLENG. contiene el nmero de caracteres de la cadena que se
acaba de reconocer.
NOTA: el ltimo caracter de la cadena es: YYTEXT[YYLENG- l ].
2. 4. 1 AIANOS EJEMPLOS DE ESPECIFICACION PARA LEX
Ejemplo 1: Como ejempolo trivial, conciderese el problema
concistente en contar todas las ocurrencias de la letra a en un
archivo de entrada y ademas producir como salida la impresin del
archivo de entrada en pantalla, slo que por cada a que aparezca
imprimiremos una A.
La espeificiacin de entrada para que Lex genere una funcin
que resuelva el problema anterior se muestra a continuacin.
int num-a=O;
a ( ++num-a;
%%
printf ("A") ;
return;
1
Se utiliza una variable de tipo entero llamada num-a la cual
es declarada e inicializada en cero antes de iniciar la seccin
de reglas de la especificacin de entrada de Lex. El delimitador
%% indica el inicio de la seccin de reglas, en el cual se
especifica que la variable nun-a debe ser incrementada cada vez
que en el archivo de entrada se encuentre una letra a y adems se
imprimir en pantalla la letra A. Las acciones por default
realizadas por Lex para las entradas no contempladas en la
especificacin de entrada es de la de impresin en pantalla. Esto
es, para cualquier caracter de entrada exepto a ser impreso en
pantalla y cuando llege el caracter a se imprimir A y se
incrementar num a, cuando termine de procesarse el archivo, el
valor de la varxable indicar el nmero de ocurrencias de la
letra a.
Ejemplo 2: Escribir la especificacin de entrada para que Lex
cuente el nmero de lineas de un archivo y no imprima nada en
pantalla.
31
int nun-lineas=O;
%)
%%
\n {++nun-lineas;)
t
En la especificacin de entrada declaramos e inicializamos la
variable num-lineas a cero; en la seccin de reglas declaramos
la
expresin regular que concuerde con el salt0 de linea, cuando en
el archivo de entrada encontramos este caracter, la accin a
realizar es incrementar la variable num-lineas exclusivamente. Si
encontramos cualquier otro caracter se ejecutar la accin de la
segunda regla de la especificacin de entrada, la cual es una
accin nula, por lo cual no realizamos nada, ni imprimimos nada a
pantalla.
Ejemplo 3: Escribir la especificacin de entrada para que Lex
genere un programa que imprima en la salida una lista con los
identificadores del archivo de entrada. Los identificadores se
deben escribir con mayusculas.
int i;
1 [a-zA-Z]
% I
d [O-91
%%
{ for (i=O;iqyleng;i++)
printf ("%s",yytext) ;
I
8
\n I
yytext[ i] =toupper (yyetxt [ i] ) ;
los corchetes en la expresin regular indican que el smbolo fue
declarada en la seccin anterior.
En la especificacion de entrada declaramos una variable entera
i; y declaramos la clase de las letras y los dgitos; en la
seccin de reglas tenemos una expresin regular que busca
identificadores (esto es cualquier cadena que comience con una
letra y sea seguida de letras o digitos), cuando lo encuentra
convierte caracter a caracter el contenido de yytext a mayusculas
e imprime el identificador en pantalla. La segunda expresin
regular concuerda con cualquier entrada que no sea un
identificador ni el caracter \n, su accin a ejecutar es nula. La
tercera expresin regular concuerda con el caracter \n y su
accin es nula.
32
Ejemplo 4: Escribir la especificacin de entrada para que Lex
genere un programa que sume todos los nmeros enteros de un
archivo de texto.
int suma=O;
$1
d [O-91
%%
{a)+ { suma =suma +atoi(yytext) t )
I
\n t
En la especificacion de entrada declaramos e inicializamos la
variable entera suma, declaramos la clase de los dgitos; en la
seccin de reglas tenemos una expresin regular que busca nmeros
enteros (esto es cualquier cadena que contenga slo dgitos),
cuando lo encuentra convierte el contenido de yytext a
un entero
y ala variable suma le asignamos su contenido ms la conversin
de yytext a entero. La segunda expresin regular concuerda con
cualquier entrada que no sea un identificador ni el caracter \n,
su accin a ejecutar es nula. La tercera expresin regular
concuerda con el caracter \n y su accin es nula.
Ejemplo 5: De acuerdo a las siguientes definiciones regulares
token asociado
t ----> l(lld)* 1
d+ 2
I = 3
dd ----> (blCR)*
donde: t indica el token a buscar.
d+ expresin regular que denota un entero
= expresin regular que denota el smbolo =
dd indica los delimitadores existentes
b indica espacios en blanco
CR indica el caracter enter.
Construir la especificacin de entrada para Lex, considerando que
el programa generado ser llamado por el analizador sintctico.
Puesto que el programa generado ser llamado por el analizador
sintctico, necesitaremos algunas variables globales que nos
sirvan de interfaz entre las dos fases:
- ap-id. Cuando nuestro reconocedor lxico se encuentre con
algn identificador, lo que debemos de hacer es introducir el
identificador a una tabla de smbolos y regresar al analizador
sintctico el nmero de token encontrado y adems en ap-id
33
regresamos la localidad de la tabla de smbolos donde insertamos
el identificador.
- valor-nun. Cuando el analizador lxico encuentra un entero
debemos de regresar el nmero de token asociado a esa expresin
regular y adems en una variable entera (valor-nun) el entero
encontrado.
%{
% I
1 [a-zA-Z]
%%
int nun-linea; /*por si ocurre error sabemos el # lnea */
i valor-num =atoi(yytext) t
1
return (3) t
t
{++nun-linea;}
{ Error(yytext,nun-linea) i }
return (2) t
En la seccin de declaracin de la especificaccin de entrada
tenemos definida la variable num-linea, la cual contendr el
nmero de lnea que se est procesando en cada instante, esto con
el fin de que si ocurre un error, adems de mandar un mensaje nos
posicionamos en la lnea donde ocurri dicho error. Adems
tenemos declarado la clase de letras. En la seccin de reglas,
tenemos la primer expresin regular que denotan los
identificadores, las acciones a realizar para esta regla son:
introducir el identificador a la tabla de smbolos (mediante la
funcin maneja-id) y regresar el nmero de token asociado al
analizador sintctico. La segunda expresin regular denota los
enteros encontrados en el archivo de entrada, cuando es activada
esta expresin, la accin a relizar es: a valor-num le asignamos
el entero encontrado y se lo regresamos al analizador sintctico
adems del nmero de token asociado. La tercer expresin busca el
smbolo =cuando lo haya lo nico que hace es regresar el nmero
de token asociado a sta expresin al analizador sintctico. La
cuarta expresin no realiza nada slo salta los espacios en
blanco. La quinta reconoce los saltos de lnea y su accin
asociada es incrementar la variable num-linea. Y la ltima
concuerda con los tokens no contemplados en las anteriores
expresiones y su accin es mandar un mensaje de error indicando
la lnea donde ocurri.
Para generar nuestro analizador lxico debemos introducir la
especificacin de entrada para Lex donde debemos considerar:
34
1.- Los tokens a reconocer, as como su nmero de token asociado.
2.- Los delimitadores y comentarios.
3.- Cuando encontremos un identificador lo debemos de introducir
a una tabla de smbolos (pues se ha encontrado una variable) y se
debe informar de esto a la siguiente fase de compilacin, esto se
realiza tilizando una variable global ap-id, la cual contiene la
direccin de la localidad de la tabla de smbolos donde se
encuentra ste identificador.
4.- Cuando encontramos una cadena de caracteres entre comillas,
es que se ha definido una cadea (si la especificacin lo marca
as), por lo tanto la debemos de introducir a una tabla de
cadenas y debemos de informar de esto a la siguiente fase de
compilacin; esto lo realizamos definiendo una variable global
poscad, la cual contiene la direccin donde se hubica la cadena,
dentro de la tabla de cadenas.
5.-En caso de encontrar caracteres invlidos, mandar un mensaje
de error.
La tabla de smbolos se recomienda que se implemente mediante
una tabla de hash donde cada entrada apunte a una lista ligada y
donde la longuitud de la tabla de hash se un nmero primo y su
funcin de insercin sea lo suficientemente eficiente para
dispersar los smbolos en toda la tabla de manera uniforme. Por
el momento los campos que debe de llevar cada nodo de la tabla
son:
- Un apuntador a una cadena (identificador). Esto con el fin de
no tener que declarar el tamao del identificador a insertar y
con esto desperdiciar memoria.
- Un apuntador al siguiente nodo.
- Campos adicionales requeridos por las dems fases de
compilacin
La tabla de cadenas se recomienda que sea un arreglo de
apuntadores a cadenas. Y poscad tendr el indice de la ltima
cadena que se insert.
2. 4. 2 EJ EMPLODE UN ANALI ZADOR LEXI COCOMPLETO.
#i ncl ude nut i l es. cn
i nt num- l i nea- 1;
8)
bl anco
l et ra
di gi t 0
i d
numero
Z
corn
C
cad
Cadena
ot ro
%%
ent ero
f unci on
pri nci pal
ent onces
si
ot ro
mi ent ras
haz
regresa
{bl anco)
\ n
{i d)
{numero)
{Cadena )
[ \tl
[ A- Za- z]
{l etra)({l etra)l {di gi to))*
(di gi to)+
I A\ nl
("S" {Z 1 *\ n)
l AN\ nl
{C)*
0-9 1
{ret urn ( ENTERO) ; )
{ret urn ( F"C10N) ; )
( ret urn( PRI NC1PAL) ; )
{ret urn ( ENTONCES) ; )
{ret urn ( SI ) ; )
{ret urn (OTRO) ; )
{ret urn ( MI ENTRAS) t )
{return(HAZ) ; )
{ret urn ( REGRESA) ; )
{num- l i nea++; )
{ap- i d =maneja-id(yytext,yyleng); /* I NTRODUCI MOS
EL I DENTI FI CADOR ALA TABLA DE S1MBOLX)S */
ret urn ( I D) ; )
{val - num=at oi ( yyt ext ) ; /* REGRESAMOS EL NM. */
ret urn (NM) ; )
{pos- cad =mete- cad(yytext); /* I NTRODUCI MOS
ret urn ( CAD) ; )
{num- l i nea++; )
{return(' ;' );}
{return(' , ' ); )
{return(' (' );)
{return(' )' );)
{ret urn( {,) ; )
{return(' )' ) ;)
I
LA CADENA A LA TABLA DE CADENAS */
36
{return (MAI)
{return(MAY)
(return(ME1)
{return (MEN)
{return ( = I )
{return (NOI )
{return (SUM)
{return (SUB)
{return (MUL)
{return (DI V)
{mane j a-errc
%%
37
CAPITULO I11 AIi1ALISADOR SIYTACTICO
3.1 GRAMATICAS INDEPENDIENTES DE CONTEXTO.
DEFINICION. Una gramtica independiente del contexto consta de 4
partes :
1.- Vn: Conjunto de smbolos no terminales.
2.- Vt: Conjunto de Smbolos terminales.
3.- P : Conjunto de producciones de la forma:
a ---> B (a "deriva" 0)
donde: a E Vn y lal-1 (longuitud de a-1)
y B E (Vn U Vt)*
4.- S : Smbolo inicial, donde S E Vn
Adems Vn U Vt =E y Vn Vt =
EJEMPLO:
V n = { S }
Vt ={ a,b }
P = { S --> ab,
S --> aSb }
El lenguaje que podemos reconocer con esta gramtica es el
siguiente:
S --> aSb --> aaSbb --> aaabbb
en general podemos reconocer el lenguaje: a+b+
DEFINICION. Una cadena w deriva directamente a una cadena 2 , si z
puede obtenerse a partir de w mediante la aplicacin de una de
las producciones de l a gramtica ( w --> z) .
Si a-->ai.-->a2-->a3-->. ..-->an, entonces a--->an
Si a--->B y B ---> g, entonces por transitividad a ---> g
*
* * *
DEFINICION. Sea una gramtica G con smbolo inicial S.
a) w es una forma sentencia1 de de G si S --- > w y w est
formada por smbolos terminales y no terminales
b) z es una sentencia de G si S ---> z y z est formada por
smbolos terminales.
*
*
DEFINICION. El lenguaje generado por una gramtica G es el
conjunto de sentencias de la gramtica
*
L(G) = { w I S --- > w s i w E V t }
38
EJ EMPLO 1: Decriba el lenguaje generado por la siguinete
gramtica:
s ---> 1
s ---> os
Derivaciones posibles:
s --> 1
os --> o1
00s --> O01
000s --> 0001
I
I
I
Analizando las derivaciones posibles observamos que el
lenguaje generado por la gramtica es el siguinte:
L(G) = { l,Ol,OOl,OOOl, ...) =0*1
EJ EMPLO 2: Describa el lenguaje generado por la siguiente
gramtica:
s --> 1
s --> s1
s --> so
Derivaciones posibles:
10 1 11
so0 <-- so <-- s --> s1 --> s11
I I I
I I
o1 s10
Analizando las derivaciones posibles observamos que el
lenguaje generado por la gramtica es el siguinte:
L(G) ={ l,ll,lO,lll,llO,lOl,lOO,...} =1(0)1)*
EJ EMPLO 3: Escriba la gramtica que genere el siguiente lenguaje:
aAn bA2nt n>= 1.
El lenguaje generado por esta expresin regular es el
siguiente :
39
L(G) = { abb, aabbbb, aaabbbbbb, ... )
Del lenguaje generado por la expresin regular podemos
reagrupar cada sentencia de la siguiente forma:
L(G) = { abb, a(abb)bb, aa(abb)bbbb, ...)
De lo cual resulta claro que la grhtica que genera dicho
lenguaje es el siguiente:
S --> abb
S --> aSbb
EJEMPLO 3: Escriba la gramtica que genere el siguiente lenguaje:
aAn bAm cAr : n par mayor que 1, m impar >=1, r>= O.
El lenguaje generado por esta expresin regular es el
siguiente:
L(G) = { aa, aabc, aabbb, aabbbc, . . . )
Del lenguaje generado por la expresin regular podemos
observar que la gramtica debe de ser de la forma
Donde:
El primer smbolo no terminal puede generar:
A = {aa, aaaa, aaaaaa, ...)
de aqu es claro que, A genera las siguientes producciones:
>aa
A ---
A ---> Aaa
El segundo smbolo no terminal puede generar:
A = {b, bbb, bbbbb, ...)
de aqu es claro que, B genera las siguientes producciones:
B ---> b
B ---> bbB
El tercer smbolo no terminal puede generar:
C = { E , c, CC, CCC, ...}
de aqu es claro que, C genera las siguientes producciones:
C ---> E
c ---> cc
144172
Del anlisis anterior podemos concluir que la gramtica generada
por la expresin regular es la siguiente:
S ---> A B C
> aa
A ---
A ---> Aaa
B ---> b
B ---> bbB
C ---> E
c ---> cc
Considere la gramtica siguiente:
S - E
E --> I
E --> I+E
I --> b
I --> a
La expresin "a+b+a" pertenece a la gramtica generada por este
lenguaje?
En cada paso de la derivacin debemos:
1.- Decidir cual smbolo de la produccin vamos a reemplazar.
2.- Decidir cual produccin sustituir del smbolo eleguido.
Esto nos llevar a dos posibles caminos de derivacin:
a) E --> I +E --> a +E --> a + I +E --> a +b +E -->
--> a +b + I --> a +b +a
a) E --> I +E --> I + I +E --> I + 1 + I --> I + I +a -->
--> I +b + a --> a +b +a
En el camino de derivacin del inciso a consideramos derivaciones
en donde slo el no terminal de ms a la izquierda fue
sustituido en cada paso, formando as una "sentencia izquierda" o
"forma de frase izquierda" de la gramtica en cuestin.
Anlogamente, para el inciso b, consideramos derivaciones donde
slo el no terminal de ms a la derecha fue sustituido en cada
paso formando as una Itsentencia derecha" de la gramtica en
cuestin. Las derivaciones derechas a menudo se denominan
derivaciones cannicas.
Un rbol de anlisis sintctico se puede considerar como una
representacin grfica de una derivacin que no muetre la
eleccin relativa del orden de sustitucin. El rbol creado por
41
las derivaciones anteriores es:
E
I
a I + E
b
I
I
I
I
a
donde: Cada nodo interior de un rbol de anlisis sintctico se
etiqueta con algn no terminal E y que los hijos de ese nodo se
etiquetan, de izquierda a derecha, con los smbolos del lado
derecho de la produccin por el cual se sustituy sta E en la
derivacin. Las hojas del rbol de anlisis sintctico se
etiquetan con no terminales y terminales y leidas de
izquierda a
derecha constituyen una sentencia llamada el producto o frontera
del rbol.
Cabe sealar que un analizador sintctico no constituye
fsicamente el rbol de parze, slo verifica sise puede
construir o no. Si se puede construir el rbol, implica que el
texto de entrada est escrito sintcticamente correcto.
3.2 ANALISIS SINTACTICO DESCENDENTE.
Existen bsicamente dos tipos de analizadores sintcticos: el
analizador sintctico descendente (Top Down), el cual intenta
encontrar una derivacin por la izquierda para una cadena de
entrada. Tambin se puede considerar como un intento de construir
un rbol de anlisis sintctico para la entrada comenzando desde
la raz del rbol y creando los nodos del rbol en orden previo,
y. El analizador sintctico ascendente (Botton up), el cual ser
tratado posteriormente.
Dentro de los analizadores sintcticos descendentes
encontramos aquellos que tilizan la tcnica de Backtrack y son
aquellos que enzayan produccin por produccin: cuando una
produccin no funciona, "deshace" todo lo hecho en es t a
produccin y "deslee" tokens para poder enzayar con la siguiente
produccin tal y como se ilustra en el siguiente ejemplo.
42
Cosidrese la gramtica
S --> c A d
A - - > a b I a
y la cadena de entrada w = cad. Para construir un rbol de
anlisis sintctico descendente para esta cadena, primero se crea
un rbol formado por un solo nodo etiquetado con S. Un apuntador
a la entrada apunta a c, el primer sinrbolo de w. Despus se
tiliza la primera produccin de S para expandir el rbol y
obtener el rbol de la figura sig.
a b a
Pasos en el anlisis sintctico descendente.
Se empareja la hoja situada ms a la izquierda, etiquetada con c,
con el primer smbolo de w, y acontinuacin se aproxima el
apuntador de entrada a rar, el segundo smbolo de w, y se
considera la siguiente hoja etiquetada con A. Entonces se puede
expandir A utilizando la primera alternativa de A para obtener el
rbol de la figura (b) . Como ya se tiene una concordancia para
el segundo smbolo de la entrada se lleva el apuntador de entrada
a 'd8, el tercer smbolo de la entrada, y se compara 'd' con la
hoja siguiente, etiquetada con 8b8. Como ' b 8 no concuerda con
'd', se indica fallo y se regresa a A para saber si existe otra
alternativa de A que no se haya intentado, pero que pueda dar
lugar a un emparejamiento.
Al regresar a A, se debe reestablecer el apuntador de entrada
a la posicin 2, aquella que tena al ir a A por primera vez, lo
cual significa que el procedimiento para A (anlogo al
procedimiento para no terminales de la figura 2.17) debe
almacenar el apuntador a la entrada en una variable local. Se
intenta a continuacin la segunda alternativa de A para obtener
el rbol de la figura (c). Se empareja la hoja 'a8 con el segundo
smbolo de w, y la hoja 'd8, con el tercer smbolo. Como ya se ha
producido un rbol de anlisis sintctico para w, se para y se
anuncia el xito de la realizacin completa del anlisis
sinttico.
Una gramtica recursiva por la izquierda puede hacer que un
analizador sintctico por descenso recursivo, incluso uno con
retroceso, entre en un l azo infinito. Es decir, cuando se intenta
expandir A, puede que de nuevo se est intentando expandir A sin
43
haber consumido ningn smbolo de entrada.
Los analizadores sintcticos con backtrack tienen la
desventaja de que son muy costosos en tieapo y espacio, adems de
que pueden entrar en ciclos recursivos. En muchos casos,
escribiendo con cuidado la gramtica, eliminando su recursin por
la izquierda y factorizando por la izquierda la gramtica
resultante, se puede obtener una gramtica analizable con un
analizador sintctico predictivo.
Un analizador sintctico predictivo o sin backtrack es aquel
que sabe de antemano que produccin va a ser utilizada; esto es,
debe llevar un token adelante para poder saber que regla de
produccin va a ocupar.
Pa ra poder construir el diagrama de transiciones de un
analizador sintctico predictivo a partir de una gramtica,
primero se debe eliminar la recursin por la izquierda de la
gramtica y despus factorizar dicha gramtica por la izquierda,
luego, para cada no terminal A se hace lo siguiente:
1.- Crese un estado inicial y un estado final (de retorno)
2.- Para cada produccin A --> Xl,X2,...,Xn crese un camino
desde el estado inicial al estado final, con aristas
etiquetadas con Xl,X2,...,Xn.
El analizador sintctico predictivo que se desprende de los
diagramas de transiciones se comporta como sigue: comienza en el
estado de inicio del smbolo inicial. Si despus de algunos
movimientos se encuentra en el estado S, y si ese estado tiene
una arista etiquetada con el terminal a de estado S al estado T,
y si el siguiente smbolo de entrada es a, entonces el analizador
sistctico cambia el cursor de la entrada una posicin ala
derecha y se va al estado T. Si, por otra parte, la arista est
etiquetada con un no terminal A. El analizador sintctico, va al
estado de inicio de A, sin mover el cursor de la entrada. Si
llega a alcanzar el estado final de A, inmediatamente va al
estado T, habiendo en efecto wleido" A de la entrada cuando se
traslad del estado del estado S al estado T. Por ltimo, si hay
una arista de S a T etiquetada con E, el analizador sintctico va
inmediatamente del estado S al T, sin avanzar la entrada.
Como ejemplo considerese la siguiente gramtica, observando el
desarrollo de su diagrama de transiciones respectivo as como su
implementacin en un algoritmo.
S : EXP
EXP --> id OP F
OP --> +
OP --> *
F --> id
F --> numero
Formemos la derivacin de la cadena na+3n.
44
El analizador sintctico lleva un token adelante para poder saber
que regla de produccin ir a ocupar
Al comenzar con el smbolo inicial, reemplazamos EXP por el
lado derecho de la produccin id OP F al hacer esto, ya
llevamos un token ledo por adelantado (a), por lo cual asociamos
la a con el smbolo terminal "idn. Adelantamos un token (+) , con
lo cual sabremos que produccin reemplazar por el no terminal OP,
que es el que est a continuacin. Al reemplazar la produccin,
avanzamos el cursor, adelantamos un token (3) e intentamos
averiguar con ste, que produccin tiliear para el smbolo no
terminal F (numero). Con lo cual obtenemos el rbol de derivacin
mostrado arriba.
Con la expresin Oa++lB, veamos como se comportar nuestra
gramtica.
ID OP F
a + ERROR DE SINTAXIS
Al comenzar con el smbolo inicial, reemplazamos EXP por el
lado derecho de la produccin id OP F al hacer esto, ya
llevamos un token ledo por adelantado (a), por lo cual asociamos
la a con el smbolo terminal "id". Adelantamos un token (+) , con
lo cual sabremos que produccin reemplazar por el no terminal OP
(OP-->+), que es el que est a continuacin. Al reemplazar la
produccin, avanzamos el cursor, adelantamos un token (+) e
intentamos averiguar con ste, que produccin tilizar para el
smbolo no terminal F (numero, 6 id). Puesto que no coincide con
nunguno de ellos, podemos concluir que la expresin no est
dentro del lenguaje reconocido por esta gramtica.
El autmata para esta gramtica se muestra a continuacin
+
45
Fig. 2.5 Automatas para una gramtica
Su implementacin se muestra a continuacin en forma de
algoritmos.
PROCEDIMIENTO EXP
Comienza
token e-- LEX;
SI token <> cod-id ENTONCES
OTRO
ERROR
Comienza
token <-- LEX
OP
F
Termina
Termina
PROCEDIMIENTO OP
Comienza
SI ( token <> cod-+ ) y ( token <> cod-* ) ENTONCES
OTRO
Termina
ERROR
token <-- LEX
PROCEDIMIENTO F
Comienza
SI (token <> cod-id) y (token <> cod-=) ENTONCES
OTRO
Termina
ERROR
token <-- LEX
Veamos ahora la siguiente gramtica, sus autmatas y su
implementacin
S : DECL
DECL --> var id TI PO
DECL --> const id
TI PO --> entero
TI PO --> caracter
de acuerdo a la gramtica tenemos los siguientes autmatas
f \ ?o
donde la implementacin de los autmatas es la siguiente:
PROCEDIMIENTO DECL
Comienza
token c-- LEX
SI token =cod-var ENTONCES
Comienza
token c-- LEX
SI token
ERROR
OTRO
Comienza
token
Termina
TIPO
Termina
OTRO
SI token
Comienza
token
c> cod-id ENTONCES
c-- LEX
=cod-const ENTONCES
c-- LEX
SI token c> cod-id ENTONCES
OTRO token c-- LEX
ERROR
Termina
OTRO
ERROR
Termina
PROCEDIMIENTO TIPO
Comienza
SI token <> cod-entero ENTONCES
OTRO
token <-- LEX
SI token =cod caracter ENTONCES
OTRO
token c-- e X
ERROR
Termina
3. 2. 1 GRAMATICAS LL(1)
Las gramticas LL(1) se denominan as por que la primera letra
"Ll 1 de U( 1) representa (por left en ingls, izquierda) el examen
de la entrada de izquierda a derecha, la segunda "Ln representa
una derivacin por la izquierda, y el uno por tilizar un
smnbolo de entrada de examen por anticipado en cada caso para
tomar las desiciones de la eleccin en el anlisis sintctico.
Toda gramtica U( 1) es aquella con la cual s se puede
construir un analizador sintctico predictivo.
Las gramticas LL(1) tienen varias propiedades distintivas.
Ninguna gramtica ambigua o recursiva por la izquierda puede ser
LL(1) adems de algunas otras propiedades que se mencionarn
conforme se valla desarrollando el siguiente ejemplo, esto, con
el fin de ser ms ilustrativos.
ASIGN --> id =DER
DER --> EXP
DER --> LLAMADA
EXP --> numero
EXP --> -EXP
EXP --> (EXP +EXP)
LLAMADA--> id()
LLAMADA--> id (PARAM)
PARAM --> id
PARAM --> id, PARAM
El diagrama de las dos primeras producciones es:
4 0
Fig. 2.6 Autmatas para una gramtica LL(1).
la implementacin del primer diagrama de transiciones para la
primer produccin se muestra en el siguiente algoritmo
PROCEDIMIENTO ASIG
token e-- LEX
SI token <> cod-id ENTONCES
ERROR
OTRO
Comienza
Comienza
token <-- LEX
SI token <> cod-= ENTONCES
ERROR
OTRO
Comienza
DER
token <-- LEX
Termina
Termina
Termina
Para poder realizar la implementacin del segundo diagrama de
tramnsiciones, lo primero que debemos de hacer es analisar el
token adelantado para ver a que produccin pertenece, si a EXP
o a LLAMADA. Para esto ocuparemos las siguientes definiciones:
DEFINICION. Sea x un smbolo de una gramtica, se define FIRST(x)
como el conjunto de todos los tokens con los cuales pueden
comenzar las cadenas derivadas a partir de x.
49
3.2.2 Calculo del FIRST(x).
1.- Si x es un smbolo terminal, entonces FIRST(x) =(x}
2.- Si x es un no terminal, se consideran todas las producciones
de x y para cada una de ellas se hace lo siguiente:
i) Si la produccin es de la forma: x --> Yl,Y2,Y3,..,Yk,
entonces se tiliza el siguiente algoritmo:
i <-- o
REPITE
i <-- i+i
Agregar todo lo que est en FIRST(Yi) al FIRST(x) ,
salvo la cadena vaca.
HASTA (i-k) 6 (la cadena vaca NOE FIRST(Yi) )
SI (i=k) Y (la cadena vaca pertenece a FIRST(Yi) ) ENT.
Agregar E al FIRST(x)
Cosiderese como ejemplo momentaneo la siguiente gramtica,
pues despus retomaremos nuestro ejemplo inicial.
E --> T G F
G --> +T G
T --> F U
U --> * F U
U --> E
F --> id
num
Calculemos ahora el FIRST de cada smbolo no terminal, de
acuerdo al procedimiento anterior
puesto que existen tres derivaciones de F
F --> id
num
I E
analicemos cada una de ellas:
FIRST(1d) = { id }
FIRST(num)= { num }
y adems la cadena vaca est derivada por F, por lo tanto:
FIRST(F) = { id, num, E }.
Puesto que existen dos derivaciones de U
analicemos cada una de ellas:
U --> * F U
U --> E
FIRST(*) = { * }
FIRST(E) = { E }
por lo tanto:
FIRST(U) = ( *, E }
Calculemos ahora el FIRST(T) , para esto, siguiendo el algoritmo,
debemos de agregar el FIRST(F) al FIRST(T) , coa0 la cadena vaca
pertenece al FIRST(F), entonces debemos de agregar tambin el
FIRST (U) al FIRST (T) , dandonos como resultado
FIRST(T) =( id, num, *, E }
Calculemos ahora el FIRST(G), puesto que slo tiene una
derivacin, y el primer smbolo de la produccin es un terminal,
concluimos que:
FIRST(G) =( + }
Por ltimo, calculemos, el FIRST(E) , puesto que slo tiene una
derivacin calculemos el FIRST de su primer smbolo de la
derivacin: FIRST(T) = { id, num, *, E ); puesto que contiene la
cadena vaca ser necesario incluir el FIRST del segundo simbolo
de la produccin al FIRST(E): FIRST(G) = ( + ), y como ste ya no
contiene la cadena vaca, hasta aqu concluye el clculo del
FIRST (E), dandonos como resultado:
FIRST(E) = { id, nun, *, + )
DEFINICION. Para clcular el FIRST(Xl,X2, ..., Xk), se procede de
la siguiente forma:
i <-- 0
REPITE
i <-- i+i
Agregar todo lo que est en el FIRST(Xi) al
FIRST(Xl,XL,...,Xk), salvo la cadena vaca.
Agregar E al FIRST(Xl,XL,...Xk).
HASTA (i-k) (La cadena vaca no pertenezca al FIRST(Xi) )
SI (i=k) Y (la cadena vaca pertence al FIRST(Xi) ) ENTONCES
Ejemplo:
FIRST(FU) = ( id, num, *, E }
FIRST(FG) = ( id, num, + }
tal como se mostr arriba.
Retomando nuestro ejemplo y gramtica inicial tenemos que el
clculo de sus FIRST'S es:
51
FIRST de la produccin
L-DA --> id (
LLAMADA --> id (
ASIGN --> id =DER
)
PARAM)
DER --> EXP
DER --> LLAMADA
EXP --> numero
EXP --> -EXP
EXP --> (EXP +EXP)
LLAMADA--> id()
LLAMADA--> id (PARAM)
PARAM --> id
PARAM --> id, PARAM
Si un smbolo no terminal A tiene varias producciones
asociadas A --> AllA2 IA3 I . . . IAk, para saber cual es el que debe
utilizarse para formar el rbol de parse de un programa de
entrada, se calculan los FIRST de los lados derechos de las
producciones y se busca el token adelantado x en ellas. Si x
pertence al FISRT(Ai) la produccin que debe utilizarse es A -->
Ai.
Si un smbolo no terminal A tiene varias producciones de la
forma A --> AllA21A31 ...)Ak, entonces FIRST(Al), FIRST(AZ),...,
FIRST(Ai) deben ser conjuntos disjuntos. Esto con el fin de saber
que produccin es la que vamos a seguir de acuerdo a al contenido
de nuestro token adelantado. Si vemos nuestra gramtica
analizada, vemos que los FIRST de la derivacin de LLAMADA no son
conjuntos disjuntos para la cual haremos uso de la siguiente
definicin.
DEFINICION. Factorizacin por la Izquierda. S una gramtica
tiene un smbolo no terminal con varias roducciones que
comparten un prefijo comn: A-->aA1 I aA2 I aA3 I . . . 1 mi I ai2 I . . .I mi,
una gramtica equivalente es:
A --> mllm2l ...I milaA'
A'--> AlIA21 ...1Ak
Factorizamos por la izquierda la produccin de LLAMADA para que
esta gramtica pertenezca alas gramticas del tipo LL(1).
52
factorizando por la izquierda, tenemos:
LLAMADA --> id (LLAMADA'
LLAMADA'--> )
UAHADA'--> PARAM)
Solo nos queda modificar las producciones del no terminal PARAM,
puesto que sus FIRST no son conjuntos disjuntos lo que d como
resultado que nuestra gramtica no sea U(1). Modificando los
ordenes de las derivaciones obtenamos una produccin equivalente
como la siguiente
PARAM --> id
PARAM --> PARAM , id
Donde los FIRST de las nuevas producciones son:
Donde el FIRST de la primera produccin es { id ), el FIRST de
la segunda produccin es el FIRST de PARM que es l mismo, por
lo tanto el FIRST de la segunda produccin es { id ). Para evitar
posibles confuciones en este tipo de producciones, veamos la
siguiente definicin.
DEFINICION. Eliminacin de la recursin por la Izquierda. Sea A
un smbolo no terminal con producciones recursivas por la
izquierda :
A --> AallAa2l . . . I AanlBll82l ...I Bm
Donde: 81, 82,...,8m no comienzan con A.
Una gramtica equivalente sin recursin por la izquierda es la
siguiente:
A --> BlA'I 82A'I ...lBmA'
A --> alA81 a2A'l ...I anA'I E
De acuerdo a la definicin enunciada arriba, eliminemos la
recursin por la izquierda de las producciones:
PARAM --> id
PARAM --> PARAM , id
Eliminando la recursin por la izquierda tenemos:
PARAM --> id PARAM'
PARAM*--> , id PARAM'
PARAM'--> E
Con sta modificacin tenemos ya totalmente una gramtica =(I),
slo nos resta realizar los algoritmos para la implementacin de
los ltimos dos smbolos no terminales, lo cual se deja al lector
53
interesado. Por lo tanto tenemos como conclusin la siguiente
gramtica U( 1)
ASIGN --> id =DER
DER --> EXP
DER --> LLAMADA
EXP --> numero
EXP --> -EXP
EXP --> (EXP +EXP)
LLAMADA --> id (LLAMADA'
LLAMADA' --> PARAM)
LLAMADA' --> )
PARAM --> id PARAM'
PARAM' --> , id PARAM'
PARAM' --> E
3.2.3 OTRAS CARACTERIZTICAS DE LAS GRAMATICAS LL(1)
1.- Si A --> AllA21 ... lAn, es una produccin, entonces FIRST(Al),
FIRST(A2) , . . . , FIRST(An) deben ser conjuntos disjuntos.
Dada la siguiente gramtica
s --> Axy
A --> X
A --> E
podemos generar los siguientes rboles de parse
f-b A X Y
I
X
S
A X Y
I
E
donde el smbolo no terminal A, produce los smbolos x 6 E.
Dada la siguiente gramtica
S --> a B cl c2 c3 ... cn
B --> B1 82 ... Bn
B --> al a2 ... an
I E
54
podemos generar el siguiente rbol.de derivacin o de parse
S
donde el smbolo no terminal B puede generar cualquiera de sus
tres producciones, en particular B puede derivar la cadena vaca,
por lo cual debemos ver si el siguiente token (FOLLOW) es cl,
despues de haber leido la entrada a, si es as el smbolo no
terminal B producir la cadena vaca, en caso contrario producir
alguna de las dos anteriores producciones. Para esto tilizaremos
la siguiente definicin.
DEFINICION. Dado un smbolo no terminal x, el FOLLOW(x) es el
conjunto de todos los tokens que pueden aparecer inmediatamente a
la derecha de x en una rbol de parse.
3.2.4 CALCULO DEL FOLLOW.
Para calcular el FOLLOW(x)
se procede de la siguiente manera:
i) Si x es el smbolo inicial, se agraga EOF al FOLLOW(x).
ii) Si hay una produccin de la forma A --> ax, se agrega todo
lo que est en FOLLOW(A) al FOLLOW(x) .
iii) Si hay una produccin de la forma A --> axB, se agrega todo
lo que est en el FIRST(B) al FOLLOW(x) salvo la cadena
vaca. Si el FIRST(B) incluye la cadena vaca, se agreg
todo lo que est en el FOLLOW(A) al FOLLOW(x).
Tmese como ejemplo la siguiente gramtica y calculese los FIRST
y los FO-WS de cada produccin.
S : A
A --> D e
A --> B C
B --> d A E
I E
E --> e E
a B A c
I E
D --> d
Siguiendo los pasos anteriore claculemos los FIRST Y FOLLOWS de
las producciones.
FOLLOW(D) = { e )
FIRST(e) = { e }
FOLLOW(B) = { C, d }
FIRST(C) = { c }
FIRST(Ac) = { c, d }
FIRST(A) ={ d, c }
PIRST(D) = { d }
FIRST(B) = { d, e }
FOLIiOW(A) = { e, a, c, d, EOF }
FIRST(E) = { e, a, E }
FOLIXIW(E) ={ C, d }
Si una gramtica incluye un smbolo no terminal A que pueda
derivar la cadena vaca, A --> Al ( A2( ...IAk( E, para decidir cual
es la produccin que debe utilizarse para formar el rbol de
parse de un programa de entrada, se lee el siguiente token x, si
x pertenece al FIRST(Ai) , la produccin que debe utilizarse es A
--> Ai; si x pertenece al FOLIXIW(A), l a produccin que debe
utilizarse es A --> E. Ejemplo:
S --> A x y
A --> X FIRST(A) ={ x }
I E
FOLLOW(A) = { x }
El FIRST(A) y el FOLLOW(A) deben ser conjuntos disjuntos.
2.- Si A --> A1IA21 ...I AnlE, son producciones, entonces,
FIRST(A1) , FIRST(A2) !. . . , FIRST(An) y el FOLLOW(A) deben ser
conjuntos disjuntos.
Ejemplo: Retomando la gramtica del ejemplo anterior, y puesto
que no comple con la segunda regla de las gramticas U( 1) y no
la podemos pasar a dicha gramtica (por factoriazacidn izquierda
o recursin); la intentaremos reescribir
esta, s es gramtica LL(1) , pues el FIRST(A) = { xI y } y el
FOLLOW(S) = { EOF }.
56
. ~~~~
3.3 ANALISIS SINTACTICO ASCENDENTE
En esta seccin se introduce un estilo general de anlisis
sintctico ascendente, conocido como anlisis sintctico por
desplazamiento y reduccin. El anlisis sintctico IiR se tiliza
en varios generadores autmaticos de analizadores sintcticos.
El anlisis sintctico por desplazamiento y reduccin intenta
construir un rbol de anlisis sintctico para una cadena de
entrada que comienza por las hojas (el fondo) y avanza hacia la
raz (la cima). Se puede considerar eete proceso como de
"reducirw una cadena w al smbolo inicial de la gragltica. En
cada paso de reduccin se substituye una cadena determinada que
concuerde con el lado derecho de una produccin por el smbolo
del lado izquierdo de dicha produccin y si en cada paso elige
correctamente la subcadena, se traza una derivacin por la
derecha en sentido inverso.
La tcnica que revizaremos para el anlisis sintctico
ascendente es de las ms eficientes (hasta ahora), la cual se
puede Utilizar para analizar una clase ms amplia de gramticas
independientes del contexto. La tcnica se denomina anlisis
sintctico LR(K); la "L" es por el examen de la entrada de
izquierda a derecha (en ingls, left to right), la "RW por
construir una derivacin por la derecha (en ingls, rightmost
derivation) en orden inverso, y la k por el nmero de smbolos de
entrada de examen por anticipado utilizados para tomar las
desiciones del anlisis sintctico. Cuando se omite, se asume que
k, es 1. El anlisis sintctico LR es atractivo por varias
razones.
- Se pueden construir analizadores sintcticos LR para reconocer
prcticamente todas las construcciones de los lenguajes de
programacin para los que se pueden escribir gramticas
independientes del contexto.
- La clase de gramticas que pueden analizarse con los mtodos LR
es un supraconjunto de la clase de gramticas que se pueden
analizar con los analizadores sintcticos predictivos.
- Un analizador sintctico LR puede detectar un error sintctico
tan pronto como sea posible hacerlo en un examen de izquierda a
derecha de la entrada.
El principal inconveniente del mtodo es que supone demasido
trabajo construir un analizador sintctico LR a mano para una
gramtica de un lenguaje de programacin tpico. Se necesita una
herramienta especializada -un generador de analizadores
sintcticos LR -. Por fortuna, existes disponibles estos
generadores, por lo tanto estudiaremos el diseo y uso de uno, el
programa YACC. Con este generador se puede escribir una gramtica
independiente del contexto y el generador produce automticamente
un analizador sintctico para dicha gramtica. Si la gramtica
contiene ambiguedades u otras construcciones dificiles de
analizar en una examen de izquierda a derecha de la entrada, el
57
generador puede localizar dichas construcciones e informar al
disador del compilador de su presencia.
Veamos ahora un ejemplo de como se realiza el anlisis
sintctico ascendente sobre una gramtica en particular.
EJ EMPLO: Considrese la gramtica
S - - > a A B e
A --> AbC I b
B --> d
La frase abbcde se puede reducir a S, por los siguientes 4
pasos :
A
a b b c d e
S
A
S
I
A B
Donde cada rbol representa cada uno de los pasos seguidos
para reducir la cadena w al smbolo inicial S. El ltimo rbol
obtenido representa todas las secuencias de pasos seguidos.
La frase abbcde se redujo a S por los siguientes pasos:
abbcde
aAbcde
aAde
aABe
S
Se examina abbcde buscando una subcadena que concuerde con el
lado derecho de alguna produccin. Las subcadenas b y d sirven.
Elijase la b situada ms a la izquierda y sustityase por A, el
lado izquierdo de la produccin A --> b; as 88 obtiene la
subcadena aAbcde. A continuacin, las subcadenas Abc, b y d
concuerdan con el lado can el lado derecho derecho de alguna
produccin. Aunque b es la subcadena situada ms a la izquierda
que concuerda con el lado derecho de alguna produccin, se elige
sistituir la subacdena Abc por A, que es el lado derecho de
la produccin A --> Abc. Se obtiene ahora aAde. Sustituyendo
despus d por B, que es el lado izquierdo de la produccin B -->
d, se obtiene aABe.
Ahora se puede sustituir toda esta cadena por
S. De hecho, estas reducciones trazan la siguiente derivacin por
la derecha en orden inverso:
S --> aABe --> aAde --> aAbcde --> abbcde.
Para formar el rbol de parse en forma ascendente las
situaciones que debemos considerar son:
1.- Qu secuencias de smbolos forman el lado derecho de una
2.- Qu produccin tilizar para formar el rbol de parse de la
produccin.
entrada.
DEFINICION. Un mango de una cadena w, es una subcadena 0 que es
el lado derecho de alguna produccin A --> B tal que al
remplazar B por A en w se obtiene una nueva cadena w' con la
cual se puede completar el rbol de parse para w.
Puesto que en muchos casos no basta tener en consideracin los
puntos anteriores devido a que en ocasiones, la subcadena situada
ms a la izquierda B que concuerda con el lado derecho de alguna
produccin A --> B produce una cadena no reducible al smbolo
inicial, como en el siguiente ejemplo:
A A
I I
a b b c d e
y de estas reducciones ya no podemos obtener el smbolo
inicial. Por lo cual siempre aplicaremos lo siguiente: Para ver
si se reduce una cadena en una cadena en una constante (smbolo
no terminal) veremos si el FOLIXIW(cte) contiene nuestro token
adelantado s es as, procederemos la reduccin; en caso
contrario no lo realizaremos y observaremos ms tokens para ver
si podemos reducir con alguna otra produccin aplicando el mismo
mtodo.
Analizamos nuevamente nuestro ejemplo anterior. Dada la
gramtica y cadena anterior veremos la primer reduccin.
59
A
I
a b b c d e
Puesto que la subcadena a no aparece del lado iequierdo de alguna
produccin; leeremos el siguiente token de la entrada, el cual es
b; puesto que la b s aparece en el lado izquierdo de una
produccin A --> b y el token adelantado b aparece en el
FOLIXIW(A) - ( b, d }; por lo tanto se procede la reduccin. A
continuacin tendremos la siguiente cadena
a A b c d e
Puesto hasta ahora tenernos la subcadena a A leida y sta no
aparece de el lado derecho de alguna produccin, leeremos el
siguiente token, la b, la buscamos del lado izquierdo de las
producciones, hallandose en A --> b; pero como el token
adelantado c no pertenece al FOLLOW(A) no haremos la reduccin.
Leeremos el siguiente token, la c, teniendo leido hasta ahora la
subcadena aAbc, vemos si esto produce un mango, lo cual es
afirmativo puesto que A --> Abc y realizamos la reduccin puesto
que el token adelantado es la d E FOLLOW(A) . Teniendo el
siguiente rbol de parse.
A
m
a A b c d e
formando la siguiente subcadena aAde, de la cual hemos leido aA;
puesto que no forma un mango, leeremos el siguiente token d,
veremos si forma un mango, lo cual es afirmativo, pues B --> d y
el token adelantado es e E FOWW(B). Por lo cual, realizaremos
la reduccin para formar el rbol
B
I
a A d e
ahora tenemos la cadena aABe de la cual hemos leido aAB, puesto
que no forma un mango, leeremos el siguiente token e, el cual no
forma un mango, pero toda la cadena hasta ahora leida s, pues, S
--> aABe, la cual se reduce al smbolo inicial. Por lo tanto, la
gramtica s es LR(l), pues se pudo formar el rbol de parse
S
I
1
A B
60
3.3.1 IMPLEMENTACION POR MEDIO DE UNA PILA DEL ANALISIS
SINTACTIC0 POR DESPLAZAMIENTO Y REWCCION.
Un modo adecuado de implantar un analizador sintctico por
desplazamiento y reduccin es mediante la utilizacin de una pila
para manejar los smbolos gramaticales, y un buffer de entrada
para manejar la cadena w que se ha de analizar. Se utiliza $ para
marcar el fondo de la pila y el extremo derecho de la entrada. Al
principio, la pila est vaca, y la cadena w est en la entrada,
como sigue:
Pila
$
Entrada
w $
El analizador sintctico funciona desplazando cero o ms smbolos
de la entrada a la pila hasta que un mango B est en su cima. El
analizador repite este lazo hasta que detecta un error o hasta
que la pila contiene el smbolo inicial y la entrada est vaca:
Pila Entrada
$5 $
Despuds de esta configuracin, el analizador se para y anuncia la
terminacin con xito del analisis sintctico.
EJEMPLO: Hgase el recorrido paso a paso de las acciones que
puede realizar un analizador sintctico por desplazamiento y
reduccin para analizar la cadena de entrada id1 + id2 * id3
segn la gramtica
E --> E +E
E --> E * E
E --> (E)
E --> id
La secuencia se muestra en el siguiente esquema. Obsrvese, que
como la gramtica tiene dos derivaciones por la derecha para esta
entrada, existe otra secuencia de pasos que puede dar un
analizador por desplazamiento y reduccin.
Pila
$
$id1
$E
$E +
$E + id2
$E +E
$ E + E *
$E +E * id3
$ E + E * E
$E +E
$E
Entrada
id1 + id2 * id3 $
+ id2 * id3 $
+ id2 * id3 $
id2 * id3 $
* id3 $
* id3 $
id3 $
$
$
$
$
61
Accion
desplazar
reducir por E --> id
desplazar
desplazar
reducir por E --> id
desplazar
desplazar
reducir por E --> id
reducir por E --> E * E
reducir por E --> E +E
aceptar
L
Aunque las principales operaciones del analizador son el
desplazamiento y la reduccin, existen en realidad cuatro
acciones posibles que un analizador por desplazamiento y
reduccin puede realizar: l)desplazar, 2) reducir, i)aceptar,
4 ) error .
1.- En una accin de desplazar, el siguiente smbolo de entrada
se desplaza a la cima de la pila.
2.- En una accin de reducir, el analizador sabe que el extremo
derecho del mango est en la cima de la pila. Entonces debe
localizar el extremo izquierdo del mango dentro de la pila y
decidir el no termina con que debe sustituir el mango.
3.- En una accin de aceptar, el analizador anuncia la
terminacin con xito del anlisis sinthtico.
4.- En una accin de error, el analizador descubre que se ha
producido un error sintctico y llama a una rutina de
recuperacin de errores.
Hay un hecho importante que justifica el uso de una pila en el
anlisis sintctico por desplazamiento y reduccin: el mango
siempre aparecer en la cima de la pila, nunca dentro.
3.3.2 CONFLICTOS DURANTE EL ANALISIS SINTACTICO POR
DESPLAZAMIENTO Y REWCCION.
Existen gramticas independientes del contexto para las cuales
no se pueden utilizar analizadores sintcticos por desplazamiento
y reduccin. Todo analizador por desplazamiento y reduccin para
estas gramticas puede alcanzar una configuracin en la que el
analizador sintctico, conociendo el contenido total de la pila y
el siguiente smbolo de entrada, no puede decidir si desplazar o
reducir (un conflicto de desplazamiento/reduccin), o no puede
decidir que tipo de reduccin efectuar (un conflicto
reduccin/reduccin). A continuacin se vern algunos ejemplos de
construcciones sintcticas que dan lugar a dichas gramticas.
Tcnicamente, estas gramticas no estn dentro de la clase LR(K).
A este tipo de gramticas se les denomina, gramticas no LR. La k
de LR(K) se refiere al nmero de smbolos de preanlisis sobre la
entrada. Por lo general, las gramticas utilizadas en compilacin
se incluyen en la clase LR(l), con un smbolo de anticipacin.
Como ejemplos ilustrativos, veamos las siguientes gramticas
EJEMPLO: Considrese la siguiente gramtica:
S 9-> y A x
s --> c x
C --> y A
A --> a
w =yax
Tenemos inicialmente la pila vaca y la cadena de entrada w.
Obtenemos el primer token y, puesto que no forma un mango,
hacemos un shift introduciendolo ala ai m de la pila. Para lo
cual tenemos
H
Leemos el siguiente token a, y vemos si forma un mango; como A
--> a y el token adelantado en este momento x E FOLIXIW(A) = ( x) ,
entonces podemos realizar la reduccin, obteniendo en la pila
a continuacin podemos hacer dos cosas reducir Ay por C, pues C
--> Ay, o bien leer el siguiente token x, hacer un desplazamiento
y despus reducir utilizando la produccin S --> yAx. A este tipo
de situaciones se les conoce como nconflictos shift-reduce" o
como "conflictos de desplazamiento/reduccin", pues en etse
momento no sabemos si desplazar o reducir ya que en ambos casos
obtendremos rboles de parse correctos
S S
Y a X
Puesto que la gramtica es ambigua, devido a que genera dos
roles distintos provocando en la pila un conflicto shift-reduce,
entonces por definicin la gramtica no es LR(1).
EJ EMPLO: Consideremos ahora la gramtica
S --> a C d
S --> E d
E --> a B
B --> x
B --> x
w =axd
Iniciamos con la pila vaca y la cadena w. obtenemos el primer
token a, puesto que no forma un mango, hacemos un desplazamiento
introduciendo el token a la cima de la pila. Para lo cual
tenemos :
leemos el siguiente token x, el cual forma un mango con la
produccin B --> x, pues el token adelantado d E FOLLOW(B) ={d).
Pero tambin
forma un mango con la produccin C --> x, pues el
token adelantado d E FOLLOW(C), por lo tanto, nos encontramos
ante el problema de que produccin utilizar para hacer la
reduccin. A este tipo de conflictos se les conoce como
"conflictos reduce-reduce" o bien como "conflictos reducir-
reducir". Ya que si continuamos con el anlisis podremos formar
dos rboles de parse distintos para la misma gramtica
S S
a X d a X d
Por io cual concluimos que la gramtica no es =(I), pues,
contiene un conflicto reducir-reducir.
En general, cualquier gramtica que contenga conflictos y
genere dos rboles de parse distintos para sta, no puede ser
m(1)
3.3.3 EL AXORITMO DE ANALISIS SINTACTICO LR(1).
En la figura siguiente se muestra la forma esquemtica de un
analizador sintctico LR. Consta de una entrada, una salida, una
pila, un programa conductor y una tabla de anlisis sintctico
con dos partes (accin e ir-a). El programa analizador lee
caracteres de un buffer de entrada de uno en uno; Qtiliza una
64
pila para almacenar una cadena de la foma SO X1 sl X2 s2 ,...,
Xm sm, donde sm est en la cima de la pila. Cada Xi es un smbolo
gramatical y cada si es un smbolo llamado estado. Cada smbolo
de estado resume la informacin contenida debajo de la pila.
Programa para
anlisis
sintctico LR
-------.,, Salida
Fig. Modelo de un analizador sintctico LR.
accin
En la seccin siguiente abordaremos el tema de como construir
las tablas de anlisis sintctico.
ir-a
3.3.4 CONSTRUCCION DE TABLAS DE ANALISIS SINTACTICO
Una gramtica para la que se puede construir una tabla de
anlisis sintctico se denomina gramtica LR.
Un analizador LR no tiene que examinar la pila completa para
sabeer cuando aparecen los mangos en la cima. Por el contrario,
el smbolo del estado en la cima de la pila contiene toda la
informacin necesaria. Es un hecho curioso que, si se puede
reconocer un mango conociendo slo los smbolos gramaticales de
la pila, entonces existe un autmata finito que puede, leyendo
los smbolos gramaticales de la pila, entonces existe un autmata
finito que puede, leyendo los smbolos gramaticales de la pila de
arriba a abajo, determinar el mango, si existe, que esta en el
tope de la pila. La funcin ir-a de una tabla de anlisis
sitctico LR es escencialmente dicho autmata finito. Sin
embargo, el autmata no necesita leer la pila para cada
movimiento. El smbolo estado almacenado en la cima de la pila es
el estado en que estara el autmata finito reconocedor de los
mangos si hubiera ledo los smbolos gramaticales de la pila
desde abajo hasta la cima. Por tanto, el analizador sintctico LR
puede determinar a partir del estado de la cima de la pila todo
65
lo que necesita saber sobre lo que hay en ella.
Existe una diferencia significativa entre las gramticas U y
las LR. Para que una gramtica sea LR(K) 8 hay que ser capaz de
reconocer la presencia del lado derecho de una produccin,
habiendo visto todo lo que deriva de dicho lado derecho con k
smbolos de examen por anticipado. Este requisito es mucho menos
riguroso que el de las gramticas LL(K), donde hay qua ser capaz
de reconocer el uso de una produccin viendo slo 108 primeros k
smbolos de los que deriva su lado derecho. Por consiguiente, las
gramticas LR pueden describir ms lenguajes que las gramticas
LL .
Un elemento del anlisis sintactico LR de una gramtica G, es
una produccin de G con un punto en alguna posicin del lado
derecho. Por tanto, la produccin A --> XYZ produce los cuatro
elementos
A --> DX Y Z
A --> XDY Z
A --> X YDZ
A --> X Y Zm
La produccin A -- E genera slo un elemento, A --> D.
Intuitivamente, un elemento indica hasta donde se ha visto una
produccin en un momento dado del proceso del anlisis
sintctico. Por ejemplo, el primer elemento de arriba indica que
se espera ver a continuacin en la entrada una cadena derivable
de XYZ. El segundo elemento indica que se acaba de ver en la
entrada una cadena derivable de X y que a continuacin se espera
ver una cadena derivable de YZ.
La idea central es construir primero a partir de la gramtica
un autmata finito determinista para reconocer los prefijos
viables. Los elementos se agrupan en conjuntos, que dan lugar a
los estados de un AFN que reconoce los prefijos viables, y el
"agrupamientoI8 es en realidad la construccin de subconjuntos.
Si G es una gramtica con smbolo inicial S, entonces G 8 , la
gramtica aumentada para G, es G con un nuevo smbolo inicial S'
y la produccin S' --> S. El propsito de esta nueva produccin
inicial es indicar al analizador cundo debe detener el anlisis
sintctico y anunciar la aceptacin de la cadena. Es decir, la
aceptacin se produce cuando, y solo cuando, el analizador est a
punto de reducir por S' --> S.
3.3.4.1 LA OPERACION CERRADURA
Si I es un conjunto de elmentos para una gramtica G, entonces la
cerradura(1) es el conjunto de elementos construidos a partir de
I por las dos reglas:
1.- Inicialmente, todo elemento de I se aade al cerradura(1).
2.- Si A --> aiBB est en la cerradura(1) y B --> r es una
produccin, entonces adase el elemento B --> mr a la
66
cerradura (I), si todava no est ah. Se aplica esta regla
hasta que no se puedan aadir ms elementos a cerradura(1).
Intuitivamente, si A --> amBB est en cerradura(1) indica que, en
algn momento del proceso de anlisis sintctico, se cree posible
ver a continuacin una cadena derivable de B como entrada. Si B
--> n es una produccin, tambin se espera ver una subcadena
derivable de n en este punto. Por esta razn se incluye B --> mn
en cerradura(1).
EJ EMPLO: Considrese la gramtica de expresiones aumentada:
E'--> E
E --> E +T
E --> T
T --> T * F
T --> F
F --> (E) I id
Si I es el conjunto de un elemento ( [E8 --> HE] ), entonces
cerradura(1) contiene los elementos
E'--> . E
E --> . E +T
E --> mT
T --> mT * F
T --> mF
F --> .(E)
F --> mid
Aqu, E8 --> mE se coloca en cerradura(1) por la regla 1. Como
hay una E inmediatamente a la derecha del punto, por la regla 2
se aaden las producciones de E con puntos en el extremo
izquierdo, es decir, E --> mE +T y E --> iT . Ahora hay una T
inmediatamente a la derecha de un punto, as que se aade T -->
mT * F y T --> DF. A continuacin, la F a la derecha de un
punto obliga a aadir F --> .(E) y F --> wid. Por la regla 2 no
se colocan ms elementos dentro de cerradura(1).
A continuacin mostramos un algoritmo que calcula la
cerradura (I) .
FUNCTION cerradura (I) ;
BEGIN
J := I;
REPEAT
FOR cada elemento A --> auB0 en J y cada produccin
B --> n de G tal que B --> mn no est en J DO
aadir B --> mn a J
UNTIL no se puedan aadir ms elementos a J;
RETURN J;
END;
67
Observese que si se aade una produccin de B a la cerradura de I
con el punto en el extremo izquirdo, entonces todas las
producciones de B se aadirn de manera similar a la cerradura.
3.3.4.2 LA OPERACION ir-a.
La segunda funcin til es ir-a(I,X) I donde I es un conjunto
de elementos y X es un smbolo de la gramtica. Se define
ir a(1,X) como la cerradura del conjunto de todos los elementos
[A---> am01 tales que [A --> amXB] est en I. Intuitivamente, si
I es el conjunto de elementos vlidos para al- prefijos viable,
entonces ir-a(1,X) es el conjunto de elementos vlidos para el
prefijo viable nX.
EJEMPLO: Si el conjunto de dos elementos ([E' --> Em], [E --> E i
+T I }, entonces ir-a(I,+) consta de
E --> E +HT
T --> iT * F
T --> iF
F --> i(E)
F --> iid
Se calcul ir-a(I,+) examinando I para buscar elementos con +
inmediatamente a la derecha del punto. E'--> Em no es uno de
estos elementos pero E --> iE +T, s. Se desplaz el punto ms
all de + para obtener {E --> E + ff) y despus se tom la
cerradura de este conjunto.
La construccin de conjuntos de elementos.
Ahora ya se puede dar el algoritmo para construir C, la
coleccin cannica de conjuntos de elementos LR para una
gramtica aumentada GI; el algoritmo es
PROCEDURE elementos (GI ) ;
BEGIN
C := { cerradura( {[S' --> i s ] } );
REPEAT
FOR cada conjunto de elementos I en C y cada smbolo
gramatical X tal que ir-a(i,x) no est vacio y no
est en C Do
UNTIL no se puedan aadir ms conjuntos de elementos a C
aadir ir-a(I,X) a C
END.
De acuerdo a la gramtica con la que hemos venido trabajando, y
siguiendo los algoritmos mostrados, obtenemos la siguiente
68
coleccin cannica de conjuntos LR para la gramtica tratada.
I O: E'--> HE
E --> iE +T
E --> iT
T --> iT * F
T --> iF
F --> i(E)
F --> mid
15: F --> idi
16: E --> E +iT
T --> DT* F
T --> . F
F --> .(E)
F --> iid
11: E'--> Ei 17: T --> T *iF
E --> E i + T F --> .(E)
F --> iid
12: E --> Ti
T --> Ti* F 18: F --> (Ei)
E --> Ei+ T
13: T --> Fi
14: F --> (DE) T --> Ti* F
19: E --> E +Ti
E --> i E +T
E --> iT 110: T --> T * Fi
T --> iT * F
T --> iF Ill: F --> ( E ) i
F --> .(E)
F --> iid
del cual obtenemos un autmata finito no determista N cuyos
estados son los elementos, con aristas proporcionadas por la
funcin ir-a aplicada a cada elemento. Dicho autmata reconoce
exactamente los prefijos viables de la gramtica, tal como se
muestra a continuacin
69
3.3.4.3 TABLAS DE ANALAISIS SINTACTIC0 LR.
A continuacin se muestra cmo construir las funciones de accin
e ir-a del anisis sintctico LR a partir del autmata finito
determinista que reconoce prefijos viables.
Dada una gramtica G, se aumenta G para producir G', y a
partir de G se construye C, la coleccin cannica de conjuntos de
elementos para G'. Se construye accin, la funcin de acciones de
analizador sintctico, e ir-a, la funcin de transiciones de
estados, a partir de C utilizando el siguiente algoritmo.
ENTRADA: Una gramtica aumentada G'.
SALIDA: Las funciones accin e ir-a de la tabla de anlisis
sintctico LR para G'.
METODO:
1.- Construyase C = { 10,11,12,...,1n } la coleccin de conjuntos
de elementos LR para G'.
2.- El estado i se construye a partir de Ii. las acciones de
anlisis sintctico para el estado i se determinan como sigue:
a) Si [A --> aiaB] est en Ii e ir-a(Ii,a) = Ij, entonces
asgnese "desplazar ji8 a accin(i,a) . Aqu, a debe ser un
terminal.
b) Si [A --> ai J est en Ii, entonces asgnese "reducir A
--> air a accin[ i,aJ para toda a en FOUOW(A) ; aqu, A
puede no ser S'.
c) si [s' --> s i ] est en ii, entonces asgnese "aceptar" a
accin[i,$].
Si las reglas anteriores generan acciones contradictorias, se
dice que la gramtica no es LR(1) . El algoritmo no consigue en
este caso producir un analizador sintctico.
3.- Las transiciones ir-a para el estado i se construyen para
todos los no terminales A utilizando la regla : si ir-a(Ii,A)
=Ij, entonces ir-a[i,A] =j.
4.- Todas las entradas no definidas por las reglas 2 y 3 son
consideradas nerrorii .
5.- El estado inicial del analizador es el construido a partir
del conjunto de elementos que contiene IS8 --> is).
EJ EMPLO: Construccin de la tabla LR para la gramtica
E'--> E
E --> E +T
E --> T
T --> T * F
T --> F
F --> (E) I id
70
7 +-
de la cual ya obtuvimos su serie cannica de conjuntos de
elementos anteriormente. Primero considrese el conjunto de
elementos IO:
IO: E'--> iE
E --> iE +T
E --> iT
T --> iT * F
T --> iF
F --> .(E)
F --> iid
El elemento F --> =(E) da lugar a la entrada accin[ O, (1 =
desplazar 4, y el elemento F --> mid a la entrada accin[O,id] =
desplazar 5. Los otros elementos en IOno dan lugar a acciones.
Ahora considrese 11:
E' --> Tm
E --> Ti* F
Como FOLiDW(E) = ( $, +, ) ), el primer elemento hace que
accin[2,$] =accin[2,)] =reducir E --> T. El segundo elemento
hace accin[2,*] = desplazar 7. Si se contina as, se obtienen
las siguientes tablas de accin e ir-a para el anlisis
sintctico.
ESTADO I accin
id + * ( 1 $ E T F
O
1
2
3
4
5
6
7
9
10
11
a
d5 d4
d6 acep
r2 d7 r2 r2
r4 r4 r4 r4
r6 r6 r6 r6
d5 d4
d5 d4
d5 d4
d6 dll
rl d7 rl rl
r3 r3 r3 r3
r5 r5 r5 r5
1 2 3
0 2 3
9 3
10
Fig. Tabla de anlisis sintctico para la gramtica de
expresiones.
De acuerdo a los algoritmos anteriores vemos que no importa cual
sea la gramtica de entrada, siempre y cuando sea LR(1)
produciremos una tabla de anlisis sintctico (accibn, e ir-a): y
71
junto con el programa conductor, la pila y la entrada,
produciremos nuestro analizador sihthctico. Dicha tabla y mMulos
nos los proporciona ya el compilador YACC, lo nico que tenemos
que hacer es darle como entrada una gramtica no ambigua y 61 nos
generar la tabla de analisis sintctico, la pila y los
algoritmos codificados en lenguaje C para manupular dicha tabla y
pila. Es decir, nos generar nuestro analizador sintctico, dicho
compilador es nuestro siguiente tpico de estudio.
3. 4 YACC (GENERADOR DE ANALIZADORES SINTACTICOS).
YACC es una generador de analizadores sintctios que recibe como
entrada una gramtica (almacenada en un archivo de texto, el que
se denominar archivo de especificacin de entrada para YACC) y
produce como salida el analizador sintctico ascendente que
reconoce las estructuras definidas por las producciones de la
gramtica de acuerdo a la siguiente figura.
compilador
de YACC ---> yytab.c
Especificacin
de entrada para ---
YACC
compilador
xxx . exe yytab.c ---- >
u
compilador
entrada ---- > de YACC ---> salida
si por alguna razn no es posible crear el analizador sinttico,
YACC envia un mensaje indicando cual es el problema.
Para invocar a YACC, desde el sistema operativo se da el comando
A:\ > yacc <nombre del archivo de especificiacin de entrada>
si no hubo errores, YACC produce como salida el analizador
72
sinttico (escrito en C) correspondiente ala gramdtica contenida
en el archivo de especificacin de entrada. El analizador
sintctico se almacena en un archivo llamado yytab.c y la funcin
que debe invocarse para realizar el anlisis sintctico de algn
programa fuente se llama yyparse().
El usuario debe proporcionar el analizador lxico que
reconozca los tokens indicados en los lados derechos de las
producciones de la gramtica; este analizador lxico (que debe
llamrse yylex) es invocado por el analizador sintctico
producido por YACC cada vez que se requiere el siguiente token de
la entrada.
Es posible asociar a cada smbolo no terminal de la gramtica
un conjunto de atributos y a cada produccin un conjunto de
acciones semnticas, de tal forma, que el programa producido por
YACC pueda realizar otras tareas a parte de las propias del
analizador sintctico, tales como anlisis senntico,
interpretacin, generacin de cdigo, etc. El programa producido
por YACC est escrito en C. Las reglas gramaticales
proporcionadas a YACC deben tener cierto formato para que YACC
pueda comprenderlas.
3.4.1 ESPECIFICACIONES BASICAS
Un nombre puede denotar tanto un token como un smbolo no
terminal; Yacc requiere que los nombres para los tokens sean
declarados como tales.
Todo archivo de especificacin consta de tres secciones:
reglas gramaticales y funciones del usuario. Las secciones se
separan con el delimitador a% . De manera esquemtica, un archivo
de especificacin para Yacc tiene el siguiente formato
declaraciones
%%
reglas gramaticales
%%
funciones del usuario
las secciones de declaraciones y funciones de ususario son
opcionales, por tanto, el formato de especificacin mnima
admisible para Yacc es el siguiente:
%%
reglas
Los espacios en blanco, tabuladores y caracteres de nueva
linea son ignorados. Los comentarios, que pueden aparecer en
cualquier lugar en que un nombre es admisible, son encerrados
entre /* y */ como en C.
73
144172
La seccin de reglas consta puede tener una o ms reglas
gramaticales. Una regla gramatical tiene la forma
LADO-IZQUIERDO : CUERPO
donde LADO_IZQUIERDO representa un no terminal y CUERPO
representa una secuencia de cero o ms noaibres y literales. Los
dos puntos y el punto y coma son signos de puntuacin para Yacct
el punto y coma es opcional.
Yacc diferencia las letras maysculas de las minsculas. Los
nombres empleados en el cuerpo de una regla gramatical pueden
representar tanto tokens como smbolos no terminales.
Una literal consiste de un cardcter encerrado entre
apostrofes. La diagonal inversa n\n tiene el mismo empleo que en
C, de modo que
\n' representa
'\r? representa
' \ ? representa
'\\? representa
\t representa
?\bt representa
\f representa
?\xxx' representa
el
el
el
la
el
el
el
el
caracter nueva lnea
regreso de carro
apostrofo
diagonal inversa
tabulador
caracter de backspace
caracter salto de hoja
caracter ascii xxx (en octal)
Por varias razones tcnicas, el carcter nulo '\ O' nunca debe
emplearse como parte de una regla gramatical.
Si existen varias reglas gramaticales con el mismo lado
izquierdo, puede emplearse la barra vertical H( vv para evitar
escribir el lado izquierdo varias veces. El punto y coma con que
termina cada regla alternativa se reemplaza por la barra
vertical. Por ejemplo, las reglas
A : B C D
A : E F
A : G;
pueden escribirse de la forma
A : B C D
1 E F
No es necesario que todas las reglas gramaticales que tengan
el mismo smbolo no terminal en el lado izquierdo aparezcan
juntas en la seccin de reglas de la especificacin, sin embargo,
si se hace as, la especificacin es ms legible y fcil de
modificar.
Si algn smbolo no terminal X deriva la cadena vaca, se
escribe la regla
x : ;
74
I. *~
Los nombres que representan tokens deben de ser declarados, la
forma ms simple de hacerlo es empleando la directiva Woken:
%token nombrel, nombrel,...
YAcc asume que todo nombre que no sea declarado en la seccin
de declaraciones es un smbolo no terminal. Todo smbolo no
terminal debe aparecer en el lado izquierdo de cuando menos una
regla gramatical.
Entre los smbolos no terminales, hay uno denominado smbolo
inicial, que es el de mayor importancia. De no indicarse de otro
modo, se asume que el smbolo inicial es el que aparece en el
lado izquierdo de la primer regla gramatical; para indicar
explicitamente el smbolo inicial, en la seccin de declaraciones
puede incluirse la declaracin
%start smbolo-inicial
El final de la entrada al analizador sintctico debe ser
sealado mediante un token especial que llamaremos marca-de-fin.
El analizador sintctico acepta una entrada si, al terminar de
formar el rbol de parse de la entrada, recibe del analizador
lxico la marcade-fin. Si la marca-de-fin aparece en cualquier
contexto, se trata de un error.
El analizador lxico incluido en la especificacin de entrada
para Yacc debe de devolver la marcade-fin al encontrar el fin
del archivo.
3. 4. 2 ACCIONES.
Con cada regla gramatical, el usuario puede asociar acciones que
se ejecutan cada vez que la regla es reducida durante el proceso
de anlisis; estas acciones pueden calcular valores y acceder a
los valores calculados por acciones previas. Ms a h , el valor
del atributo asociado a algn token puede utilizarse en dichas
acciones.
Una accin debe encerrarse entre llaves, como sigue:
A : '(' B ')' { funcion(1,"abc"); )
o bien
xxx : YYY zzz
{
1
printf ("Ocurri YYY ZZZ") t
bandera =1;
son reglas gramaticales con acciones.
75
Es posible asociar atributos a los smbolos no terminales de
la gramtica y emplear los valores de 108 atributos en las
acciones. El valor del atributo del smbolo no terminal del lado
izquierdo de una produccin se denota en Yacc mediante la
pseudovariable $$.
Por ejemplo, una accin que no hace nada ms que asignar el
valor 1 al atributo asociado al smbolo no terminal del lado
izquierdo es
Para denotar los valores de los atributos de smbolos no
terminales colocados del lado derecho de una produccin, se
emplea la siuiente notacin:
$1 es el valor del atributo del primer SrPbolO del lado derecho
$2 es el valor del atributo del segundo smbolo del lado
derecho.
As para la produccin
A : B C D ;
$2 denota el valor del atributo asociado a C y $3 el valor del
atributo del smbolo D. como ejemplo ms concreto, considrese la
regla
expr : '(' expr ')' { $$ =$2 1
Salvo que se especifique otra cosa, el valor del atributo del
smbolo no terminal del lado izquierdo de una regla es el valor
del atributo del primer smbolo del lado derecho de sta. As,
para reglas gramaticales de la forma
A : B ;
generalmente no es necesario definir una accin explcita.
En los ejemplos anteriores, todas las acciones se han indicado
al final de las reglas. En ocasiones, es deseable realizar alguna
accin en algn lugar dentro del lado derecho de una produccin;
as, para la regla
A : B
{S $ = 1 1
( x = $ 2 ; 2 =
C
el efecto de las acciones es
valor asociado al smbolo no
Las realas con acciones
$3: 1
asignar a x el valor de 1, y a z el
terminal C.
semnticas que no se encuentren al
final de una regla son modificadas por Yacc, quien crea un nuevo
smbolo no terminal que derive la cadena vaca y la accin
76
interior es colocada al final de la nueva regla. As Yacc trata
el ejemplo anterior como si en realidad tuviera las siguientes
reglas:
A : B NUEVO-SIMBQL C
( x =$2; 2 =$3; }
NUEVO-SIMBOIX) : /* cadena vaca */ ($$ =l;}.
En el archivo de especificacin de entrada para Yacc el
usuario puede definir otras variables para ser empleadas por las
acciones. Las declaraciones y definiciones pueden aparecer en la
seccin de declaraciones encerradas entra 108 delimitadores %( y
%}. Estas declaraciones y definiciones tienen un alcance global,
de modo que son conocidas tanto por las acciones como por el
analizador lxico. Por ejemplo la definicin
%( int variable-0; %)
puede colocarse en la seccin de declaraciones, haciendo que la
variable sea accesible a todas las acciones y al analizador
lxico. El analizador sintctico generado por Yacc emplea
variables globales cuyo nombre comienza con el prefijo yy; el
usuario debe evitar nombres que inicien con dicho prefijo.
El usuario debe proporcionar a Yacc un analizador lxico que
lea la entrada y encuentre los tokens presentes de la misma. El
analizador lxico es una funcin que devuelve un entero y debe
llamarse yylex. El valor entero devuelto por la funcin es el
cdigo del siguiente token. Si se desea devolver un valor
adicional asociado a dicho token, ste puede asociarse a la
variable externa llamada yyval.
El analizador sintctico y el lxico deben asignar los mismos
cdigos a los tokens para hacer posible la comunicacin entre
ellos. Para realizar esto selecionamos los tokens y debemos de
fijarnos que concuerden los de los dos analizadores. En el cdigo
generado por Yacc se define con algn valor nmerico cada token
de manera global, debido a esto no hace falta definirlo en el
analisis sintactico pues los dos archivos fuente se van a unir en
uno slo. De acuerdo a esto, y a lo expuesto presentamos un
ejemplo de una gramatica para el analisis sintctico, la cual
nos servir para trabajar con ella en las siguientes dos
secciones para generar as sus acciones semnticas y de
generacin de cdigo.
77
3. 4. 3 GRAMATICA.
PROGRAMA
CTES
DECLS
DECL
L I STA-I D
FUN-DECLS
FUN-PRINC
FUN-DECL
ENCA
ARGS
CUERPO
LISTA-INS
I NS
INSTS
COND
EXP
TERMINO
: CTES DECLS FUN-DECLS F"-PRINC
: /* cadena vaca */
I CTES id =string
: /* cadena vaca */
I DECLS DECL
: entero LISTA-ID
: id
I LISTA-ID , id
: /* cadena vaca */
I FUN-DECLS FN-DECL
: funcionprincipal () CUERPO
: ENCA DECLS CUERPO
: funcion id( ARGS )
I funcion id ()
: id
I id , ARGS
: { LISTA-INS }
: I NS
I I NS i LISTA-INS
: id =EXP
si COND entonces INSTS otro INSTS
si COND entonces INSTS
mientras COND haz INSTS
regresa ( EXP )
: I NS
I CUERPO
: EXP REWP EXP
: TERMINO
I EXP ADOP TERMINO
: FACTOR
FACTOR
LISTA-EXP
RELOP
ADOP
MUWP
I TERMINO MULOP FACTOR
: id
i d 0
id ( LISTA-EXP )
numero
( EXP 1
: EXP
I EXP , LISTA-EXP
: >= I <= I > I < I = I <>
: + I -
: * I /
CAPITULO VI. AblALISI8 8-100
El presente captulo tiene como finalidad dar un breve repaso
sobre la fase de compilacin de anlisis sem4ntico. A partir de
este captulo reduciremos el material de estudio y nos
concentraremos en la manera de como realizar las siguientes
fases, tomando como material de apoyo el compilador Yacc.
A partir del presente captulo supondr-os (en realidad as lo
es), que todos los smbolos no terminales de la gramtica tienen
algn atributo asociado, por default se considera que el atributo
de estos smbolos es del tipo entero.
Considere la siguiente gramtktica, los atributos de los
smbolos no terminales y sus respectivas acciones semnticas.
Atributo Reglas de la Gramtica Acciones semnticas
Val E : E + T
I T
Val T : T * F
I F
E.val <-- El.val +T.val
E.val <-- T.val
T.val <-- Tl.val * F.val
T.val <-- F.val
Val F : numero F.val <-- Valor-nun
Nota: Los ndices de l os smbolos no terminales dentro de las
acciones semticas se utilizan solamente para no causar confusin
por si el smbolo pertenece al lado derecho o izquierdo de la
produccin. As , cuando existe el mismo smbolo de ambos lados de
la produccin, los del lado derecho utilizarn subindices en
orden consecutivo.
Tomemos como entrada la cadena w = 3+4*2. Para comenzar el
analizador lxico entrega el token leido, nhero, al analizador
sintctico, este hace un shift introduciendolo en la cima de la
pila el cual forma un mango con la produccin F: numero, por lo
cual el atributo de F, F.val tomar el valor de 3 despus de
realizada la reduccin. Ahora tenemos en la pila el shbolo no
terminal F, el cual tambin forma un mango con la produccin T :
F, por lo que la accin semntica a realizar es: a T.val le
asignamos F.val, despus de realizada la reduccin. Ahora tenemos
T en la cima de la pila, el cual forma un mango con la produccin
E : T, por lo que la accin semntica a realizar es: a E.val le
asignamos T.va1, despus de realizada la reduccin. As
continuamos sucesivamente hasta obtener el siguiente rbol de
derivacin con sus respectivos atributos.
80
E .va1=3
T. val=3
F. val=3
I
I
I
I
F. va114
3
I
I
F.valo2
I'
T . va158
F.val=2
4 * 2
Fig. 4.1 Arbol de anlisis sinttico con BUS respectivas acciones
semnticas.
DEFINICION. Un atributo de un smbolo no terminal x es
sintetizado s su valor depende de los valores de los atributos
de los hijos de x en el rbol de parse.
Para ilustrar la anterior definicin veamos el siguiente
e j emplo :
Ejemplo 2: Considrese la siguiente gramtica con sus respectivos
atributos de los smbolos no terminales y sus acciones
samnticas.
Atributo Reglas de la Gramtica Acciones semnticas
Tipo T : entero
Tipo L : id
I caracter
I L , id
T.tipo <-- ent
T.tipo <-- car
ap id^.tipo <-- L.tipo
ap-id^.tipo <-- L.tipo
LlTtipo <-- L.tipo
Nota: cuando el analizador lxico encontre un identificador lo
introducir a la tabla de smbolos, entregando al analizador
sintctico el apuntador a la localidad de la tabla de smbolos
donde se encuentra este (ap-id).
ai
Tomemos como entrada la cadena w = "entero a,bn. Al leer el
primer token y hacer el shift, nos damos cuenta que formamos un
mango con el smbolo de la cima de la pila (entero), por lo que
realizarnos la reduccin de acuerdo ala produccin T : entero; su
respectiva accin semntica nos indica que a T.tipo le asignamos
ent. Leemos el siguiente token (a), y lo errplazaiaos a la cima de
la pila, puesto que forma un mango de acuerdo ala produccin L :
id, realizamos la reduccin, y su accin semntica es: le
asignamos a ap-idA.tipo L.tipo (ntese que el atributo de L lo
igualamos en la primer regla al atributo de T, por lo tanto
L.tipo = T.tipo = ent). Continuamos as hasta obtener el
siguiente rbol de derivacin junto con sus respectivas acciones
semnticas.
S
I
1
T.tipo=ent
entero
r
L.tipo=ent
+a
a I b
Fig. 4.2 Arbol de anlisis sinttico con sus respectivas acciones
semnticas.
DEFINICION. Un atributo de un shbolo no terminal x es heredado
s su valor depende de l os valores de los atributos de los
hermanos o del padre de x en el rbol de parse.
De acuerdo a las definiciones concluimos que en el ejemplo 2 el
smbolo no terminal T es del tipo sintetizado, pues el valor de
ste depende del token leido (su hijo) i y el smbolo no terminal
L es del tipo heredado, pues su atributo depende del atributo de
T (su hermano en la derivacin del rbol de parse).
DEFINICION. Alas producciones con atributos y reglas semnticas
se les llama "Definicin dirigida por la sintaxisBg.
Si revizamos l os ejemplos anteriores nos damos cuanta que no
indicamos en que momento se deberan ejecutar las acciones
semnticas, pues siempre las colocamos al final; por lo cual
82
utilizaremos el mismo ejemplo 2, para indicar cuando se
e j ecutarhn
EJ EMPIX) 3:
DECL : T (L.tipo <-- T.tipo) L
T : entero (T.tipo <-- ent)
I caracter (T.tipo <-- car}
L : id (ap-id^.tipo <-- L.tipo}
I (Ll.tipo <-- Lotipo} L , id (ap-id^.tipo <-- Lotipo}
A este tipo de especificacin, junto con las acciones semticas
se le conoce como "Esquemas de Traduccinn.
EJ EMPLO 4: Ahore veremos como ejemplo una pequea gramtica que
con sus acciones semnticas, convierte un programa escrito en
pascal a uno escrito en C.
Analizando la estructura de los programas en Pascal, resulta
claro que parte de su gramtica es la siguiente
%%
PROG
DECL
LISTA
TIPO
CUERPO
INS
COND
o
o o
o
I
. .
. .
DECL CUERPO ll.ll
var LISTA ii:li TIPO " t H
id
LISTA ll,n id
integer
character
begin INS end
while COND do INS
id = id
Anexando las acciones semnticas tenemos la siguiente gramtica.
83
~
%%
PROG
DECL
LISTA
TIPO
CUERPO
INS
COND
.
.
I
I
.
.
DECL { printf(98main()\nn); } CuERPo "."
var LISTA f 8: 88 TIPO { printf(w8s e;\nn,Tipo.t,
id { Lista.1 =yytext }
LISTA II,II id ( Lista.1 =concat(Lista.1, I) n,yytext) ;
integer ( Tip0.t =nintn }
character { Tip0.t ="char"}
begin { printf(" { \nw) } INS end ( printf(" }\n) }
while { printf("whi1e ( ): } COND do INS
Lista.1); }
id ( printf("%s ",yytext) } {printf(n=-n) }
id { printf(Il%s ) n,yytext) }
Si introducimos el siguiente fragmento de programa escrito en
Pascal
Var
begin
a,b: integer;
while a =b do
end .
obtendramos como salida el siguiente fragmento de programa
escrito en lenguaje C
int a,b;
main ()
{
while (a==b)
De acerdo a la seccin 3.4.2 para la especificacin de entrada
para el compilador Yacc, un smbolo no terminal tiene por default
un atributo entero. Si se desea declarar un atributo distinto al
de default se debe de realizar de la siguiente manera: se declara
el tipo o tipos y se introducen en una unin tal como se ilustra
en el siguiente ejemplo
EJEMPLO 5: Mostremos un esquema de traduccin en una
especificacin de entrada para Yacc.
84
De acuedo a lo visto en el capitulo anterior, el atributo del
smbolo no terminal del lado izquierdo de la produccin lo
identificamos con $$ y los atributos de los smbolos no
terminales del lado derecho de la produccin los identificamos
con $1,$2,...,$n.
%1
% I
A
E
T
/* Declaracin de los atributos */
struct nodo { /* apuntador a un nodo de una tabla de hash,
char *apnombre; /* apuntador al nombre de la
struct nodo *sig-nodo:
*/
utilizada como tabla de smbolos
variable */
/* apuntador al siguiente nodo */
1
struct val-1 { /* declaracion de nmeros o literales */
int val; /* si fue nmero, guardamos su valor */
struct nodo *1[10];
1
/* si fue literal la guardamos aqui */
typedef struct nodo* tipo-A
typedef struct val-1 tipo-ET
%union 4
%type <tipol> A /* declaramos el atributo del smbolo no
%type <tipo2> E /* declaramos el atributo del smbolo no
%type <tipo2> T /* declaramos el atributo del smbolo no
terminal A, del tipo 1. */
terminal E, del tipo 2. */
terminal T, del tipo 2. */
/* gramtica con acciones semnticas */
id {$$=ap-id) E { printf(w%sw,$$->ap nom);
if ($$.val != O ) printf(N%dw,$4.val):
E 8+8 T { $$.val =$l.val + $3.val;
$$.l =concatena($l.l,$3.val): )
T { $$ =$1 1
id { $$.val =O:
$$.i[o] =ap-id;
85
$$.1[1] =NULL;
$$.l[O) =NULL:
1
1
1 nun { $$.val =valor-nun;
Nota: Cuando Yacc encuentra acciones enmedio de los smbolos no
terminales, sustituye estas por un smbolo no terminal inventado
por l. Este smbolo no terminal forma una nueva produccin la
cual es colocada al final; el smbolo no terminal deriva la
cadena vaca y realiza las acciones eliminadas de l a regla
anterior. Modificando as la forma de programar los atributos.
Un ejemplo de ello es la produccin
A : id {$$=ap-id) '=' E { printf(N9sw,$$->ap-nom);
if ($$.val 1s O ) printf(n2dN,$4.val);
< la accin semntica que se ecuentra entre id y *=' ser
reemplazada por el smbolo no terminal X, el cual derivar la
cadena vaca y realizar las acciones semnticas eliminadas, esto
es I
A : id X E ( printf(ll%sll,$$->ap-nom) t
if ($$.val != O ) printf(H%d11,$4.val);
Nosotros podemos realizar esto, para ganar claridad, pues ahora
$$ en la produccin derivada por X, es el atributo de X y no de
A, el atributo de A lo referenciaremos por $<tipo>$# donde tipo
es el tipo del atributo del smbolo A.
Adems, para referenciar a los atributos de los simbolos no
terminales de la regla de A, dentro de la regla de X, se har de
la siguiente manera: se coloca en vez de $x# $<tipo>-x, en
donde x=O,1,2,...,nf x indicar el lugar del smbolo
referenciado, en donde O indica el smbolo inmediatamente a la
izquierda de l (en este caso id), 1 indicar el segundo smbolo
encontrado a la izquierda de l, etc. , y <tipo> es el tipo del
smbolo no terminal X.
Por lo cual debemos de introducir esta regla con sus acciones
semnticas de la siguiente manera
A : id X E { printf("%c@@,$$->ap-nom);
if ($$.val != O ) printf(H%d1q,$4.val) t
86
A continuacin explicaremos las acciones semnticas de la
gramtica dada como ejemplo al final del captulo 3 y en el
siguiente captulo produciremos el cdigo de dicha graiatica.
Las especificaciones semnticas requeridas para nuestra gramtica
son :
1.- Que una variable no sea declarada ms de una vez.
2.- Que una variable se declare antes de usarse.
3.- Que el nmero de argumentos de una llamada sea igual al
nmero de argumentos de una funcin.
4.- Que las constantes tipo string slo se utilizen como
parametros
5.- Que en una llamada de la forma f(xl,x2,. ..,xn), f haya sido
declarada como una funcin.
ias reviciones semnticas que realizaremos son:
1.- Declarar un identificador antes de usarlo.
2.- No declarar un identificador ms de una vez.
3.- Revizar los tipos de operandos.
4.- Revizar el nmero de argumentos.
Para esto nos ayudaremos de algunas variables globales como:
- Declaracin: contendr el valor de 1, si al revizar el archivo
de entrada esperamos que la variable sea
declarada y contendr el valor de O, si esperamos
que la variable ya haya sido declarada, pues se
va a ocupar en alguna expresin.
- Es-Global : contendr el valor de 1, si el lugar en donde se
encuentra declarada la variable es de mbito
global, y contendr O, en caso de ser de mbito
local.
y adems utilizaremos algunas funciones, entre ellas una para el
manejo de identificadores la cual ser ejecutada por el
analizador lxico, el cual regresar al analizador sintctico el
apuntador a la localidad donde insert el identificador, o error
en otro caso.
87
F"CI0N maneja-id (id)
COMIENZA
SI (declaracin =1) ENTONCES /* declaramos */
SI (EsGlobal =1) ENTONCES /* de manera global */
Comienza
ap <--busca(id,tabla-global); /*priiero lo buscamos*/
SI (ap =NULL) ENTONCES /* si no est */
ap <--Inserta(id,tabla-global) /* lo insertamos*/
OTRO
ERROR /* si est, es que ya fue declarado */
Termina
OTRO
Comienza /* declaramos de manera local */
ap <-- busca(id,tabla-local) /* prinmro lo buscamos */
SI (ap =NULL) ENTONCES /*si no est lo insertamos */
ap <--Inserta(id,tabla-local): /*en la tabla local*/
ERROR /* si est, es que ya fue declarado */
OTRO
Termina
OTRO /* si no declaramos, es que lo vamos a usar */
Comienza /* en alguna expresin */
SI (Es-Global =1) ENTONCES
Comienza
ap <--busca(id,tabla global) /*lo buscamos esperando*/
SI (ap =NULL) ENTONCES /* que est */
Comienza
ERROR /* si no est, marcamos error y */
ap <--Inserta (id, tabal-global)
Termina
/* lo insertamos*/
Termina
OTRO
Comienza
ap <--busca(id,tabla-local) /*lo buscamos esperando*/
SI (ap =NULL) ENTONCES /*que est de manera local*/
Comienza
ap <--busca(id,tabla-global); / * o de manera global*/
SI (ap =NULL) ENTONCES
Comienza
Termina
ERROR /* si no est, marcamos error y */
ap <--Inserta(id,tabal-local); /*lo insertamos*/
Termina
Termina
Termina
Termina
Funcin para el manejo de identificadores (simbolos).
aa
Introduciremos las siguientes acciones semntfcas a una parte de
la gramtica presentada al final del captulo 3
S1 : Declaracin <-- 1;
Es-Global <-- 1;
S2 : Es-Global <-- O;
S3 : Declaracin <-- O;
54 : Es-Global <-- 1;
S5 : Limpiar-Tabla(tab1a-local);
As parte de nuestra gramtica con sus respectivas acciones
semnticas quedara:
PROGRAMA : (Sl) CTES DECLS FUN-DECLS FUN-PRINC
CTES : CTES id (Sl) = string ;
DECLS : DECLS DECL I E
DECL : entero 1 LISTA-ID ;
LISTA-ID : id (S2)
I LISTA-ID , id (S2);
FUN-DECLS: FUN-DECLS FUN-DECL I E
FUN-PRINC: funcionprincipal() ( S4) CUERPO
FUN-DECL : ENCA DECLS ( 53) CUERPO (S5)
ENCA : (Sl) funcion id (S2) ( ARGS )
I (Sl) funcion id (S2) ( )
ARGS : id
I id , ARGS
de esta parte de la gramtica slo una regla genera un conflicto
reducir-reducir, es la regla de ENCA, veamoslo en su diagrama de
transiciones
f
IO: ENCA : ifuncion id $$1 ( ARGS )
1
I ifuncion id $$2 ( )
\ I
funcion
IO: ENCA : funcion mid $$1 ( ARGS )
1
I funcion mid $$2 ( )
ENCA : funcion id i$$i ( AF2GS )
I funcion id m$$2 ( )
funcion
I funcion id i $ $ 2 ( )
conflicto
reducir-reducir
Para eliminar este conflicto tenemos que modificar un poco la
gramtica de entrada para Yacc.
Nota: Para ver todos los conflictos generados por nuestra
gramtica y reportados por Yacc, tenemos que invocar a Yacc desde
el sistema operativo de la siguiente manera:
a:\> yacc -u <archivo>
Yacc producir como salida un archivo llamado youtput, el cual
contendr todos los conflictos de nuestra gramtica.
Adems de modificar la gramtica para Yacc, agregaremos a los
nodos de la tabla de smbolos los siguientes campos: tipo, nmero
de argumentos, y otros ms que posteriormente se irn mencionando
conforme se vayan necesitando. Est o con la finalidad de facilitar
las acciones y revisiones semnticas
La regla modificada de nuestra gramtica quedar:
90
ENCA : funcion i d (52) ( F
F : ARGS (ap-fun->num-args =$1: 1
{ap-fun->num-args =O; }
I )
ARGS : i d (S2) ($$ =1:)
I i d (S2) , ARGS { $$ =1 + $4: )
si vemos, l a regla ARGS no generar conZlicto pues el token
adelantado puede deducir cual es l a produccin que evaluar.
A continuacin veremos otra parte de l a gradti ca y l e anexaremos
sus acciones semnticas como:
S5 : ap-fun =ap-id:
S6 : SI (ap fun->num-args <> $4) ENTONCES ERROR
s7 : $$ =etero
La otra par t e de l a gramtica quedar
INS : i d {S7} = EXP {SI ($4 <>entero) ENT ERROR: }
I regresa ( EXP ) {SI ($3 <>entero) ENT ERROR; )
COND : EXP RELOP EXP { SI ($loentero) o ($3oentero)
ENT ERROR:
OTRO $$ =entero: }
EXP : TERMINO { $$ =$1 )
I EXP ADOP TERMINO ( SI ($loentero) o (S3oentero)
ENT ERROR:
OTRO $$ =entero: }
TERMINO : FACTOR ( $$ =$1; }
I TERMINO MULOP FACTOR { SI ($loentero) o (S3oentero)
ENT ERFIOR;
OTRO $$ =entero: }
FACTOR : i d { $$ =ap - id->tipo; }
I i d() ( SI (ap id->num-args <> O) ENT ERROR
OTRO 3s entero;
I i d (S5) ( LISTA-EXP ) {S6}
I numero { $$ =entero: 1
I ( EXP 1 { $$ =$2: 1
LISTA-EXP : EXP { $$ =1; }
I EXP , LISTA-EXP { $$ =1 +$3: }
91
En el siguiente captulo veremos como introducir las acciones
de generacin de cdigo a nuestra especificacin de entrada para
Yacc, lo iremos haciendo paso a paso tal y como hems estado
introducciendo las acciones semntica., para que al final
mostremos el compilador completo con todas las fases contempladas
dentro de la gramtica de entrada.
92
CAPITULO 5 OENBRACION DS COD160
La fase de generacin de cdigo es la ltima y para esto
generaremos cdigo para una mquina y un procesador en
particular, tomaremos para esto la familia de proce8adores del
8086. Para esto necesitaremos tener algunoe antecedentes sobre el
lenguaje ensamblador de la &quina, por lo tanto a continuacin
daremos una breve explicacon sobre dicho lenguaje.
5.1 LENGUAJE ENSAMBLADOR PARA EL 808808086.
El procesador 8086 puede tener como mximo lmb. de memoria de
entre los cuales sern repartidos de la siguiente manera:
COMMAND. COM
SIST. OPARTIVO
1
I UTILES PARA
364 Kbf
TECLADO, PUERTOS, ETC .
-> 1 Mb.
Fig. 5.1 Divisin de la memoria del 8086.
Tiene 14 registros algunos de ellos de propsito general como:
algunos de direccionamiento y de apuntadores como
SI : source index. Se utiliza para marcar el inicio de una cadena
DI : destination index. Se utiliza para marcar el destino de una
SP : stack pointer. Indica el aputador a la pila.
a copiar
cadena a copiar
cs 1
DS
ss
BP > ..X
ES
I P : Instruction pointer. Contiene la direccin de la siguiente
instruccin a ejecutarse.
BP : base pointer. Se utiliza para el manejo de la pila, sobre
todo para el paso de parametros por el stack.
4
var a,b; (globales1
[bp+2]+SS {x local}
los registros de segmento (almacenan la direccin de inicio del
segmento)
CS : code segment. Contiene la direccin de inicio del segmento
DS : data segment. Contiene la direccin de inicio del segmento
SS : stack segment. Contiene la direccin de inicio del segmento
ES : extra segment. Contiene la direccin de inicio del segmento
de cdigo.
de datos.
de stack.
extra utilizado generalmente para datos.
y por ltimo tenemos el registro de banderas.
Cuando realizamos una llamada de una funcin en nuestro
archivo de entrada a ser compilado, lo que realizaremos es
guardar los parametros de la funcin, la direccin de retorno,
las variables locales y el registro de activacin en el stack. En
DS guardaremos las variables globales. Por lo cual los modos de
direccinamiento que utilizaremos en nuestro cornpilador sern:
Direccionamiento directo: para variables globales (DS).
Direccionamiento base: para variables locales (SS).
Fig. 5.2 Variables globales y locales dentro de la memoria.
94
veamos algunas de las operciones del lenguaje ensamblador
MOV destino, fuente : destino c-- fuente
operandos: registro, direccin y constantes.
e j emplo :
reg
MOV reg , dir
cte
MOV dir , reg
cte
para referenciar nuestras variable ejemplificadas en la figura
5. 2 tenemos que hacerlo as
globales: a, b,...
locales : x-2, y-2 o bien y-r, donde r es el nmero de bytes
desde el BP hasta donde se localiza la variable y en el SS.
de tal forma que para intercamibiar el valor de a con el de x
tendramos que hacer algo similar a lo siguiente:
MOV AX, A
MOV BX, [ BP+2]
MOV A, BX
MOV [BP+2], AX
ADD destino, fuente ; destino c-- destino + fuente
SUB destino, fuente : destino c-- destino - fuente
ejemplo; x =a+2-y
MOV AX, WORD PTR A
ADD AX, 2
SUB AX, WORD PTR [BP- 21
MOV WORD PTR[BP+2], AX
IMUL OP ; DX: AX C- - AX * OP
donde op puede ser un registro o una localidad de memoria
ejemplo: a<-- x*2, donde a es global y x es local.
MOV AX, WORD PTR[BP- 2]
MOV BX, 2
95
I ML BX
MOV WORD PTR A, AX
IDIV OP : AX <-- DX:AX / OP
donde op puede ser un registro o una localidad de memoria
ejemplo: a <-- x / bt
MOV AX, WORD PTR[BP- 21
CWD
IDIV WORD PTR B
MOV WORD PTR A, AX
CWD pasa el contenido de AX a la pareja DX:AX
J MP ETI QUETA ; Los saltos incondicionales, con este
gnemotctnico tendrn un alcance de 2 a la 15 bytes, hacia arriba
y la misma cantidad menos 1 hacia abajo nicamente.
CMP OP1, OP2 ; bandera <-- opl - op2.
El resultado se de esta operacin se reflejar en las banderas,
esto es, si el resultado fue mayor que cero, menor que cero,
igual a cero, etc.
Esta operacin se realiza para posteriormente poder hacer
saltos condicionales, dependiendo si una condicin se cumple o
no, las gnemotcnicos para los saltos condicionales son: J G,
J L, J GL, J LE, J E, J NE, etc., y tienen un alcance de 128 bytes de
cdigo hacia arriba y 127 bytes decdigo hacia abajo.
PUSH OP; Introduce el operando al tope del stack, y lo que
realiza en relidad es lo siguiente:
SP <-- SP-2
MEM[SP] <-- OP
POP OP; Saca el operando del tope del stack, y lo que realiza en
relidad es lo siguiente:
OP <-- MEM[SP]
SP <-- SP+2
CALL funcin ; Realiza una llamada a una funcin para
ejecutarse, antes de hacer esto realiza el procesador los
96
siguientes pasos:
IP <-- IP + N ; N contiene el desplazamiento de la
PUSH IP
n P funcin
: etiqueta a donde se va a saltar.
Codifiquemos ahora en ensamblador nuestras estructuras de control
SI A>B ENTONCES A =A+i OTRO BPX
en ensamblador quedara codificado:
MOV Ax, A
CMPAx, B
JG ENTONCES
JMP OTRO
ENTONCES: ADD WORD PTR A, 1
JMP FIN-SI
OTRO : MOV AX, WORD PTR[BP-21
MOV WORD PTR B, AX
FIN-SI : ....
MIENTRAS A>B HAZ A =A-1;
en ensamblador quedara codificado:
MIENTRAS: MOV AX, WORD PTR A
CMP AX, B
J LE FIN M
SUB WORD PTR A, 1
JMP MIENTRAS
FIN-M : ....
97
5.2 GENERANDO CODIGO.
Para la fase de generacin de cdigo de nuestro compilador,
necesitamos tener los siguientes campos en cada nodo de nuestra
tabla de smbolos: nombre, tipo, num-args, si g, offset,
num-var-locales, global. Esto para facilitar la generacin de
cdigo.
Introduciremos una nueva variable global llamada "offset" y
algunas acciones de generacin de cdigo como:
s1 : ap-fun c-- ap-id;
52 : ap-fun->nun-locales <-- $2;
S3 : offset c-- offset - 12
Por lo que parte de nuetra gramtica con las opciones de
generacin de cdigo quedar
DECLS : ;
I DECLS DECL { $$ =$1+$2; )
DECL : entero LISTA-ID ; { $$ =$2; }
LISTA-ID : id { $$=1;
ap-id->offset =offset;
offset =offset-2;
I LISTA-ID , id ; { $$si;
ap id->offset =offset:
offset =offset-2;
1
FUN-DECL : ENCA DECLS (S2) CUERPO
ENCA : funcion id {Sl) ( ARGS ) (S3)
I funcion id ()
ARGS : id { ap-id->offset =offset;
I id { ap-id->offset =offset;
offset =offset-2;
offset =offset-2;
1
1
, ARGS
y as continuamos sucesivamente hasta obtener la siguiente
especificacin de entrada para Yacc.
98
5.3 EJEMPm DE UN COMPI LADOR COMPLETO.
/* Archivo con las especificaciones de entr8ba para YACC y con
las acciones semanticas y de generacin de cdigo requeridas
*/
NODO ap-fun;
NODO ap loc;
NODO apIasig;
void inic-tab simb() ;
int declaradon;
int es-global ;
int ap-id;
extern NODO tab simb-loc[TAMSIMB];
extern FI LE *AS%;
extern char tab-~ad[MAXCAD][80];
extern int pos-cad;
int offset=O;
extern int val-num;
extern int num-linea;
unsigned i;
REGISTRO reg, emiteadop () , emite-mulop () ;
void escribe-op(), emitegush(), emite-salto-neg();
void gen-etiqo, emite-asig(), emite-compara();
%start programa
%token ENTERO FNCI ON PRI NCI PAL
%token ENTONCES SI OTRO MI ENTRAS HAZ REGRESA
%token ID NM CAD
%token MA1 MAY ME1 MEN NO1
%token SUM SUB ML DIV
%union (
int tipoi;
tipoexp tipo2;
char cad[l4];
I
%type <tipol> decls
%type <tipol> decl
%type <tipol> lista-id
%type <tipol> args
%type <tipol> lista-exp
%type <tipol> adop
%type <tipol> mulop
%type <tipol> relop
%type <tipo2> exp
%type <tipo2> termino
%type <tipol> factor
%type <cad> X1
%type <cad> X2
%type <cad> X3
99
c
%%
programa
ct es
decl s
decl
l i sta- i d
f un- decl s
f ungr i nc
f un- decl
: ( decl araci on =TRUE:
es- gl obal =TRUE;
f pri nt f (ASM, NI NCLDE I NI CI O. Asn\ nN) :
f pr i nt f ( ASM, WATOS SEGXENT PBLi C\ nn) ;
} ct es decl s ( f pri nt f (ASH, NMTOS ENDS\ nn) :
) f un- decl s
f ungr i nc {f pr i nt f ( ASM, %ET PRI NCI PAL : \ nn):
f pri nt f (ASM, VOP- si \ nPOP dx\ nPO
cx\ nPOP bx\ nPOP ax\ nRET\ n"):
f pri nt f ( ASM, VRI NCI PAL ENDP\ nN) :
f pri nt f ( ASM, NCODI GOENDS\ nn) ;
f pri nt f (ASM, %ND ej ecut a") : }
f pr i nt f ( AsH8NaDI mSEGMEhf T WBLI C\ nn) :
:
: /* vaci a */
I ct es I D ( ap- i d- >t i po =cad:
f pri nt f ( ASM, "%s LABEL BYTE\ nn, ap i d- >si mbol o) ; }
f =f CAD 1 . f ,
( f or ( i - O: i cst rl en (tab- cad [ peg- cad] ) : i++)
f pri nt f ( ASM, DB %d\ nn, tab- cad[pos- cad]
f pri nt f (ASM," DB O\ n") t )
#
: /* vaci a */ ($$=O;)
I decl s decl ($$=$1+$2:)
:
: ENTERO l i sta- i d ' t f ($$=$2; }
:
: I D ($$=l;
ap- i d- >t i po =ent ;
ap i d- >of f set =of f set:
of f set -= 2:
i f ( ap- i d- >gl obal ==TRUE)
f pri nt f ( ASM, "%s DW O\ nn, ap i d- >si mbol o) ; }
-
I l i sta- i d f , f I D ($$-$l+l;
ap- i d- >t i po =ent ;
ap- i d- >of f set =of f set :
of f set -= 2;
i f ( ap- i d- >gl obal ==TRUE)
f pri nt f ( ASM, N%s DWO\ n", ap- i d- >si mbol o)
:
: /* vaci a */
I f un - decl s f un- decl
:
: FNCI ON PRI NCI PAL '(' O ) f ( decl araci on =FALSE:
es- gl obal =TRUE:
f pri nt f ( ASM, nPRI NCI PAL PROC NEAR
f pri nt f ( ASM, "J SH ax\ nPSH bx\ nP
cuerpo
:
: enca decl s ( decl araci on =FALSE;
ap- f un- >no- l ocal es =$2;
f pri nt f (ASM, "MOV ax, O\ nn) :
f or ( i s0 : i <$2 : i ++)
f pri nt f (ASM, "WSH ax\ nn) :
100
) cuerpo {inic-tab-sinb(tab-simb-loc);
decl araci on =TRUE;
es- gl obal =TRUE;
fpri ntf(-, nRET-%s : \ nn, ap- f un- >si mbo
f pri ntf (ASB , nADD SP, %d\ nn, 2*ap- f un- >no
f pr i nt f ( ASM, VOP si \ nPOP dx\ nPOP cx\ nP
f pri ntf (ASM, @@bs ENDP\ nn, ap- f un- >si nbol
1
;
enca : F"C1ON I D
{ap- i d- >ti po =f un;
ap- f un =ap- i d;
es- gl obal =FALSE;
f pri nt f ( ASM, n%s PROC NEAR\nu,ap-fun->simboio);
f pri nt f ( ASM, nPUSH ax\ nPUSH bx\ nPSH cx\ nPSH dx\ nPUSH si \ n
) '(' paramet ros
;
paramet ros : {of f set =-2;) ar gs ' ) * {ap- f un- >nogar =$2;
of f set -= 12; )
I 1)' {of f set =O; ap- f un- >nogar =O;)
I
ar gs : I D {$$ =1;
ap i d- >t i po =ent ;
ap- i d- >of f set =of f set ;
of f set -= 2; )
I I D {ap- i d- >t i po =ent ;
ap i d- >of f set =of f set ;
of Tset -= 2; ) 8 , ' ar gs
I
cuerpo : ' {' l i sta- i ns ')'
l i sta- i ns : i ns
I i ns ';' l i sta- i ns
t
I
x1
C$ =1 + $7; )
i ns : I D {i f (ap- i d- >ti po != ent)
maneja-error(ap-id->simbolo,num-linea,4);
ap- asi g =ap- i d; } '=I exp
{i f ($l . ti po != ent)
SI cond ENTONCES X1 i nst s OTRO X2 i nst s ( f pri nt f ( ASM, "%s :\
SI cond ENTONCES X1 i nst s ( f pri nt f ( ASM, n%s : \ nn, $4); }
MI ENTRAS X3 cond HAZ X1 i nst s ( f pri nt f ( ASM, nJ MP %s\ nn, $2) ;
manej a- error ("ENASI GNACI ONn, nu- l i nea, 5) ;
emite-asig(ap-asig,$4);}
f pri nt f ( ASM, "%s : \ nn, $5) : }
manej a error ( "EN I NSTRUCCI ON
I REGRESA '(' exp ')' {i f ($3. ti po != ent)
regre
f pri nt f (A&, "MOV di , ") ;
escri be- op(S3) ;
f pri nt f ( ASM, "J MP RET- %s\ n", ap- f un- >si m
t
: /* vaci a */ {emite-salto-neg($<tipol>-1);
gen- eti q ( $$I :
f pri nt f ( ASM, "%s\ nn, $$) ; }
I
101
x2
x3
insts
cond
exp
termino
factor
: /* vacia */ {gen-etiq($$);
: /* vacia */ (gen-etiq($$);
: ins
I cuerpo
: exp reiop exp (if ($lotipo I = ent 1 1 $3.tipo I = ent)
emite compara($i,$3) t
fprintf(ASM,nJMP %s\na,$$);
fprintf(ASM,n%s :\nu,$<cad>-2);)
fprintf (ASM, a%rs : \na, $$) t )
;
manej a-error ("EN OPERACION DE COMPARACION
$<tipol>$ =$2;)
t
: termino ($$ =$1;)
I exp adop termino (if ($l.tipo I = ent 1 1 $3.tipo != ent)
maneja-error(aEN OPERACION r+r O 8-1w I
$$.tipo =entt
reg =emite-adop($1,$2,$3) ;
$$.clase =REG;
$$.registro =reg:)
I
: factor ($$ =$1;)
I termino muiop factor {if ($i.tipo I = ent 1 1 $3.tipo != ent)
$$.tipo =entt
reg =emite-muiop($i,$2,$3);
$$.clase =REG;
$$.registro =reg;)
maneja-error("EN OPERACION 8* r O
;
: ID ($$.tipo =ap-id->tipo;
$$.clase =MEM;
$$.pos =ap-id;)
I ID ' ( ' (ap-loc =ap-id;
fprintf (ASM, "PUSH BP\nn) ; )
argumentos ($$.tipo =ent;
fprintf(ACM,"MOV BP,SP\nN)t
fprintf(ASM,nADD BP,%d\nn,2*ap-loc->nogar);
fprintf (ASM, VALL %s\n", ap-loc->simbolo) :
fprintf (ASM, "WOV SP,BP\n") ;
fprintf (ASM, 9vpoP BP\nn) ;
$$.clase =REG;
$$.registro= dit)
I NUM ($$.tipo =ent;
I exp 8 ) 1 {$$ =$% I
$$.clase =CTE;
$$.valor =val num;)
,
argumentos : ')' (if (ap-id->nogar != O)
manejaerror("EN LLAMADA AF"CIONn,num-linea,7);)
I lista-exp ' ) ' {if (ap-ioc->noqar != $1)
maneja-error(wEN LLAMADA A FNCIONn,num-l
;
lista-exp : exp ($$ =1;
emitejush ($1) ; )
I exp 8,8 (emitegush(S1);) lista-exp ($$ =1 + $4; )
102
t
relop : MA1
ME1
MAY
MEN
NO1
f = f
:
I SUB
:
I DIV
f
adop : SUM
mulop : MUL
($$ =MUL;)
{$$ =DIV;}
donde las funciones requeridas, como mulop, adop, etc., se
muestran a continuacin.
/* La funcion emite-adop() genera codigo ensamblador para las
operaciones de suma y resta. Recibe los atributos de los
operandos: opl y op2; y tambien el codigo de la operacion: op.
*/
REGISTRO emite-adop(tipoexp opl, int op, tipoexp op2)
{
REGISTRO reg;
REGISTRO
void escribe-op (7;
asigna reg ( ) ;
if (opl.clase != REG){
reg =asigna-reg() ;
fprintf(ASM,"MOV %s, ",nom-reg[reg]);
escribe-op (opi) ;
reg =opl.registro;
fprintf (ASM, "ADD I *) ;
fprintf (ASM, "SUB i
1
else
if (op===SUM)
else
f print f ( ASM, ll%s,
escribe-op (op2 ) ;
if (op2.clase==REG)
return (reg) ;
, nom-reg [ reg] ) ;
ocupado[op2.registro]=FALSE;
1
/* La funcion asigna-reg () busca algun registro libre entre
[ax,bx,cx,dx,si], que se va a utilizar para guardar algun
operando al generar codigo para la suma-resta, multiplicacion-
103
division, los PUSH, las comparaciones y las asignaciones cuando
no se puede utilizar el modo inmediato en alguna de estas
operaciones. */
REGISTRO asigna-reg()
{
REGISTRO ind=axt
while (ocupado[ind]==TRUE && ind<5)
if (ind<5)(
ind++ t
ocupado[ind] =TRUE:
return ( ind) :
1
else( /* Esto no debera suceder en nuestro compilador */
printf ("TODOS i DS REGISTROS OCPADOS\nPRffiRAMA ABORTADOn) t
exit (1) t
1
1
/* La funcion escribe-op() se utiliza principalmente en las
instrucciones MOV para las transferencias de memoria a registro,
de registro a registro, de constante a registro y de registro a
memoria o constante a memoria. */
void escribe-op (tipoexp op)
{
if (op.clase==CTE)
else
fprintf (ASM, ss%d\n" ,op.valor) t
if (op.clase==REG)
else
fprintf(ASM,n%s\n1~,nom_reglop.reg[op.registro])t
if (op.pos->global==TRUE)
fprintf (ASM, "%s\nn,op.pos->simbolo) t
else
fprintf (ASM,*l[BP%d]\nml,op.pos->offset) t
1
/* La funcion emite mulop() efectua una operacion similar a
la de
emite-adop(), solo %e para la multiplicacion, el primer operando
debe de estar en ax, el resultado quedara en ax y dx(de ser
necesario); para la division el primer operando de bebe estar en
ax y dx y el resultado queda en ax. Por lo tanto debemos
asegurarnos que los operandos y el resultado queden en el
registro correcto. */
REGISTRO emite-mulop(tipoexp opl, int op, tipoexp op2)
{
REGISTRO busca-reg ( ) t
REGISTRO regl, reg2, reg3;
BOOLEAN salvo-ax=FALSEt
104
if (ocupado[ax]==TRUE) /* ax debe estar libre para guardar a opl */
/* Si op2 esta en ax, guardamos op2 en otro registro que no sea dx
if (op2.clase-REG && op2.registro==ax)(
regl =busca-reg() t
fprintf(ASM,"MOV %s,ax\na,nom~reg[reglJ)t
op2.registro=regi;
/* Si opl no esta en ax, guardamos en la pila lo que este en ax
if (opl.clase!=REG I I opl.registro!=ax)(
1
else
fprintf (ASM, "PUSH ax\n") t
salvo-ax=TRUE;
1
/* Guardarnos opi en ax si es que aun no esta hai */
if (opl. clase! =REG) (
1
else
fprintf (ASM, W O V ax, #I) ;
escribe-op (opl) :
if (opl.registro!=ax) (
1
/* Si op2 en dx, copiamos op2 a otro registro */
if (op2.clase==REG && op2.registro-dx)(
regl =busca-reg() t
fprintf(ASM,w8MOV %s,dx\nn,nom-reg[reglJ):
op2.registro=regi;
fprintf(ASM,VIOV ax,8s\n~~,nom~reg[opl.registroJ):
ocupado[opl.registroJ=FA~,sE:
/* Si dx esta ocupado: */
if (ocupado [ dx] )
1
/* S i op2 no esta en dx guardamos en la pila lo que este en dx */
else(
}
fprintf (ASM, "PUSH dx\n") t
salvo-dx=TRUE;
/* Para IMUL y IDIV op2 no puede ser una constante */
/* en este caso debemos pasar op2 a un registro */
if (op2. clase==CTE) (
regl =busca-reg ( ) ;
fprintf(ASM,WOV %s,%d~~,nom~reg[regl],op2.valor);
1
/* Escribimos el NMONICO para la operacion */
if (op==M~)
else(
1
/* Escribimos el segundo operando op2 */
if (op2.clase==CTE)
else
fprintf (ASM, "IMUL Ig) t
fprintf (ASM, "CWD\nn) t
fprintf (ASM, IIIDIV @I) i
fprintf (ASM, lt%s@*,nom-reg[regl)) i
105
escribe-op (opZ) t
/* Si guardamos el contenido de dx en la pila,
ponemos lo que
este en dx en otro registro y sacamos de la pila el valor
anterior de dx. Lo mismo se hace para ax. */
if (salvo dx) (
reg2 =-busca-reg ( ) t
fprintf(ASM,"MOV %s,dx\nn,nom-reg[reg2]);
fprintf (ASM, "POP dx\nw) t
reg3 =busca-reg() t
fprintf(ASM,%OV %s,ax\nn,nom-reg[r~3]);
fprintf (ASM, "POP ax\nn) t
1
if (salvo-ax) {
1
if (opl.clase==REG && opl.registro!=ax)
if (op2. clase-REG)
else
/* Retornamos el resultado de la operacion */
if (salvo ax)
else(
1
/* Liberamos los registros que ya no guardan nada importante */
ocupado[opl.registro]=FALSE;
ocupado[op2.registro]=FALSE~
if (opZ.clase==CTE)
ocupado[regl]=FALSE;
return(reg3 t
ocupado[ax]=TRUE;
return (ax) t
1
/* La funcion buscarego es similar a la funcion asigna-reg() ,
solo que en este caso los registros ax y dx se suponen no
disponibles. */
REGISTRO busca-reg()
{
if (!ocupado[bx])
return (bx) t
else
if (!ocupado[cx])
return (cx) t
else
if (!ocupado[si])t
return (si) t
printf ("REGISTROS OCUPADOS\n PROGRAMA ABORTADO\n") i
exit (1) t
/* La funcion emite-push() se utiliza al hacer el paso de
parametros, antes de llamar a una funcion.
*/
void emitegush (tipoexp op)
106
{
REGISTRO reg;
/* No se puede hacer un PUSH de una constante, antes hay que
if (op.clase==CTE){
pasar esta a un registro. */
reg =asigna-reg() ;
fprintf (MM,"MOV %~,~~,nom-reg[regJ) i
escribe-op (op) i
fprintf(MM,"PUSH %s\nm,nom-reg[regJ)i
ocupado[reg]=FALSE;
if (op.clase==REG)
1
else
fprintf(AS~,w~~~ %s\n",nom-reg[op.registro~) t
else
if (op. pos->global==FAiSE)
else
fprintf(ASM,"PUSH WORD PTR [BPld]\nw,op.pos->offset);
if (op.pos->tipo==ent)
fprintf (ASM,llPSH WORD PTR %s\n",op.pos->simbolo) t
/* Si el operando es el identificador de una cadena, hay
que hacer el PUSH del OFFSET de ese identificador */
else{
reg =asigna-reg() ;
fprintf(ASM,WOV %s,OFFSET %s\nn,nom-reg[regJ ,
fprintf (ASM,VSH %s\ntl,nom-reg[regJ) t
ocupado[reg]=FALSE;
op . pos->aimbolo) t
1
/* La funcion emite salto-neg() escribe en el archivo de codigo
ensamblador el NMONTCO para el salto condicional cuando generamos
codigo para las sentencias SI, SI OTRO y MIENTRAS. */
void emite-salto-neg(int op)
{
switch (op) {
case MAI : fprintf (ASM,"JL ") t
case ME1 : fprintf(ASM,"JG n):
case : fprintf (ASM,"JNE ,I) t
case MAY : fprintf (ASM,lIJLE ") t
case MEN : fprintf (ASM,"JGE ") t
case NO1 : fprintf (ASM,l@JE I,) ;
break:
break;
break;
break t
break;
1
1
/* i,a funcion gen etiq genera una etiqueta para utilizarla en los
saltos condicioales para las sentencias: SI, SI OTRO y
107
{
1
/*
de
MIENTRAS. */
void gen-etiq(char etiq[l4])
char etiql[l4];
char etiq2 [ 141 i
etiq[O]='\O'i
etiqi[0]='\o8;
etiq2[O]='\08;
num-etiqueta++; /* global */
itoa(num-etiqueta,etiql,lO)i
strcat (etiq2, "EH) i
strcat(etiq2,etiql);
strcpy ( et iq , et iq2 ) i
La funcion emite-asigo genera codigo para las instrucciones
asignacion de la forma id =exp donde: id es una variable de
memoria global o local y exp puede ser una variable de memoria
global o local, una expresion aritmetica, un numero entero, o
una llamada a funcion. */
void emite-asig(NODO opl, tipoexp op2)
{
REGISTRO reg;
if (op2.clase==MEM)(
reg =asigna-reg() i
fprintf (ASM, "MOV %s , U, nom-reg [ reg] ) i
escribe op (op2) i
if (opl=>global==TRUE)
else
fprintf(ASM,WOV WORD PTR %s,%s\nn,opl->simbolo,
nom-reg [ reg J ) i
fprintf(ASM,"MOV WORD PTR [BP%d],b\nn,
opl->offset,nom-reg[reg]):
ocupado[reg]=FALSE;
if (opl->global==TRUE)
else
escribe-op (op2) i
if (op2.clase==REG)
1
else{
fprintf (ASM, I'MOV WORD PTR %s, ", opl->simbolo) i
fprintf(ASM,"MOV WORD PTR [BP%d],n,opl->offset);
ocupado[op2.registro]=FA~~E;
1
1
/* i,a funcion emite-compara() genera codigo para las expresiones
en que se deben comparar dos expresiones. Es exactamente si
hicieramos una resta, pero no se retorna ningun resultado, solo
se modifica el registro de banderas.*/
108
void emite-compara(tipoexp opl, tipoexp opa)
{
REGISTRO reg;
if (opl.clase != REG)(
reg =asigna-reg() ;
fprintf (ASM, mmMOV $ 8 , I n, nom-reg[ reg J ) t
escribe-op (opl) ;
reg =op1.registro;
1
else
fprintf (ASM,WMP %~,~~,nom-reg[reg]) ;
escribe-op (op2 ) ;
if (op2 . clase==REG)
ocupado[reg]=FALSE;
ocupado[op2.registro]=FALSE;
1
109
BIBLIOGRAFIA.
Aho, Sethi, Ulman, Vompilers principles techniques and design
toolsn, Addison Wesley.
Aho, Hopcrof t , and Ulmman, @@Data structures and
algoritmsn,Addison Wesley, Reading, Xass., 1974.
Backhouse, %intax of programming lenguages theory and practice1@,
Prentice Hall.
Trembly, Sorenson, "The theory and practice of compiler
writting" , Mc Graw-Hill.
Manual de Referencia de Yacc.
Manuel de Referencia de Lex.
110

Você também pode gostar