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