Algoritmos usados por el Generador de Analizadores Sintácticos Capítulo 4: Algoritmos usados por el Generador de Analizadores Sintácticos 4.1 Introducción En este capítulo se presentan los algoritmos usados por el Generador de Analizadores Sintácticos SLR. Se tratará de clarificar bien los conceptos teóricos utilizados, lo que no hace la bibliografía que se utilizó (luego de la lectura de la bibliografía, en el mejor de los casos quedan muchos conceptos flotando y sin relación alguna, y lo más común es que no se los entienda). Los analizadores sintácticos generados trabajan con la técnica SLR(1) o SLR para simplificar, la que es un caso especial de LR(k). A diferencia de la técnica LR(1) canónica y de la técnica LALR, esta técnica genera tablas mucho más pequeñas (se minimiza el número de estados del autómata de pila). Se la puede aplicar a casi todas las gramáticas de contexto libre que pueda necesitar un programador (el desarrollo de lenguajes de programación con gramáticas complejas puede requerir trabajar con la técnica LALR, pero si la técnica SLR es aplicable entonces el analizador será más eficiente). La técnica LR ha sido descripta por Knuth [1965], pero no resultó práctica porque el tamaño de las tablas que se debían construir eran demasiado grandes (recordar que en ese tiempo 16 kbytes era mucha memoria). En 1969 Korenjak mostró que mediante esa técnica se podían producir analizadores sintácticos de tamaño razonable para las gramáticas de los lenguajes de programación. En 1969 y 1971, DeRemer inventó los métodos "LR simples" (SLR por Simple LR) y "LR con símbolo de anticipación" (LALR por lookahead-LR) que son más simples que los de Korenjak. La utilización de la técnica SLR (y del resto de las LR) simplifica mucho el trabajo del desarrollo de un traductor, siempre y cuando se disponga de un programa generador de las tablas de análisis sintáctico. Si no se dispone de un generador de las tablas, la construcción de las mismas puede llegar a desalentar la construcción del traductor puesto que se requiere un dominio profundo de los algoritmos de construcción y de la teoría en la que se basan. Una persona puede entender bien los algoritmos de construcción de las tablas, y aún así cometer muchos errores. 28 Algoritmos usados por el Generador de Analizadores Sintácticos El analizador SLR es uno solo para todas las posibles gramáticas. Es un autómata de pila determinístico, y su funcionamiento es análogo al autómata finito determinístico. El algoritmo se lo ilustra en la sección siguiente. La tabla construida para un analizador SLR se llama tabla SLR, la gramática para la cual se pueda construir una tabla SLR se llamará gramática SLR. Una gramática para la cual la construcción de la tabla SLR genere conflictos no es SLR, aunque se puedan resolver a mano los conflictos. 4.2 Análisis sintáctico por la técnica SLR (LR Simple) Antes de explicar cómo se construyen las tablas es necesario ver primero cómo se las usa. 4.2.1 Algoritmo de análisis sintáctico SLR Se dispone de una pila en la que se pueden almacenar estados (enteros no negativos) o símbolos de la gramáticas (terminales o no terminales). También se dispone de un par de tablas a las que se les da el nombre de acción e ir_a. La tabla acción contiene las acciones que puede ejecutar el autómata. Las acciones se especifican con una letra que especifica la acción y el número de estado al que debe ir el autómata luego de ejecutarla. Las mismas pueden ser: 1. Desplazamiento: se simboliza con la letra d y un número que indica el estado al que debe pasar el autómata. 2. Reducción: se simboliza con la letra r y un número que indica el número de regla por la cual se reduce. 3. Aceptar: se simboliza con la letra a. No tiene un número de sufijo puesto que no hace falta; el autómata se detiene automáticamente al encontrar esa orden. 4. Nada: si una entrada en esta tabla no contiene alguna de las tres alternativas anteriores entonces en la posición actual hay error de sintaxis. 29 Algoritmos usados por el Generador de Analizadores Sintácticos La tabla ir_a contiene números enteros no negativos que son los estados a los que tiene que ir el autómata luego de ejecutar una reducción. La figura siguiente muestra el algoritmo de análisis sintáctico LR simple: Se dispone de una pila vacía en la cual se coloca 0 como estado inicial. Sea pt el puntero al primer terminal de la cadena de entrada. Salir = No Repetir Sea E el estado en el tope de la pila y t el terminal apuntado por pt. Si acción[E, t] = dY entonces Apilar primero t y luego Y. Avanzar pt al siguiente terminal. Sino, si acción[E, t] = rX entonces X es el número de regla por la que hay que reducir, entonces desapilar 2 veces el número de símbolos de la parte derecha de la regla número X. Sea Y el estado que ahora está en el tope de la pila y A el no terminal de la parte derecha de la regla número X. Apilar primero A y luego ir_a[X, A]. Sino, si acción[E, t] = a entonces Se ha llegado al estado de aceptación. Salir = Si. Aceptar = Si. Sino Salir = Si. Aceptar = No; hay error de sintaxis. FinSi Hasta que Salir = Si. Si Aceptar = Si entonces La oración de entrada cumple con la estructura definida por la gramática, esto es, es gramatical. Sino La oración de entrada no es gramatical. 30 Algoritmos usados por el Generador de Analizadores Sintácticos Fig. 10 Algoritmo de análisis sintáctico SLR 4.2.2 Ejemplo del funcionamiento del analizador sintáctico SLR Para ejemplificar el funcionamiento del analizador sintáctico trabajaremos con la gramática siguiente (las reglas han sido numeradas): 1: 2: 3: 4: 5: 6: 7: P D D L L I I --> --> --> --> --> --> --> D I 'Var' L ':' 'Tipo' ';' L ',' 'Id' 'Id' I 'Instr' ';' Las tablas de análisis sintáctico SLR para la gramática dada son las siguientes: | acción(estado, símbolo terminal) | ir_a(estado, símbolo no | | terminal) Est | 'Var' ':' 'Tipo' ';' ',' 'Id' 'Instr' FA | P D L I ----+--------------------------------------------+----------------------0 | d1 · · · · · r3 r3 | 2 3 · · 1 | · · · · · d4 · · | · · 5 · 2 | · · · · · · · a | · · · · 3 | · · · · · · r7 r7 | · · · 6 4 | · r5 · · r5 · · · | · · · · 5 | · d7 · · d8 · · · | · · · · 6 | · · · · · · d9 r1 | · · · · 7 | · · d10 · · · · · | · · · · 8 | · · · · · d11 · · | · · · · 9 | · · · d12 · · · · | · · · · 10 | · · · d13 · · · · | · · · · 11 | · r4 · · r4 · · · | · · · · 12 | · · · · · · r6 r6 | · · · · 13 | · · · · · · r2 r2 | · · · · Fig. 11 Tabla de análisis sintáctico SLR para la gramática de un lenguaje simple. En la tabla acción el símbolo FA significa Fin de Archivo. Aunque no forma parte del texto de entrada, su inclusión fue realizada durante la construcción de las tablas; el símbolo Fin de Archivo es un símbolo terminal de la gramática ampliada (ver más adelante qué es una gramática ampliada). Sea el texto de entrada el siguiente: Var Id : Tipo ; Instr ; 31 Algoritmos usados por el Generador de Analizadores Sintácticos El análisis sintáctico se ilustra en el cuadro siguiente. Se utiliza un espacio como separador de elementos de la pila. Si se ha llegado al fin de archivo (FA) en la columna Entrada no habrán terminales. Paso 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Pila 0 0 Var 1 0 Var 1 Id 4 0 Var 1 L 0 Var 1 L 5 0 Var 1 L 5 : 7 0 Var 1 L 5 : 7 Tipo 10 0 Var 1 L 5 : 7 Tipo 10 ; 13 0D 0D3 0D3I 0D3I6 0 D 3 I 6 Instr 9 0 D 3 I 6 Instr 9 ; 12 0D3I 0D3I6 0P 0P2 Entrada Var Id : Tipo ; Instr ; Id : Tipo ; Instr ; : Tipo ; Instr ; : Tipo ; Instr ; : Tipo ; Instr ; Tipo ; Instr ; ; Instr ; Instr ; Instr ; Instr ; Instr ; Instr ; ; Acción d1 d4 r5 Ir a 5 d7 d10 d13 r2 Ir a 3 r7 Ir a 6 d9 d12 r6 Ir a 6 r1 Ir a 2 Acepta r Fig. 12 Ejemplo de análisis sintáctico con la técnica SLR. La pila del analizador puede quedar con muchos elementos y aún así haber llegado al estado de aceptación. 4.2.3 Clasificación de los errores que se pueden producir durante el análisis sintáctico El algoritmo de análisis sintáctico SLR presentado en la figura 10 no especifica qué tipos de errores pueden aparecer ni cómo tratarlos. En esta 32 Algoritmos usados por el Generador de Analizadores Sintácticos sección se dará una definición de los tipos de errores que pueden aparecer ya que la bibliografía no da una definición rigurosa de los mismos. Error lexicográfico: se dará este nombre al error que se produce al intentar buscar un símbolo terminal siguiente al actual y se encontró un símbolo que no es parte del conjunto de símbolos terminales de la gramática. El Fin de Archivo (FA) no es error lexicográfico, sino que simplemente no hay más símbolos terminales de entrada. Error semántico: si la implementación del analizador sintáctico incluye la posibilidad de ejecutar acciones semánticas (ver sección 2.3.8 y 12.3.3), durante la ejecución de esas acciones se pueden producir errores. A esos errores se les dará el nombre de errores semánticos. Error sintáctico: si no es error lexicográfico ni error semántico entonces es sintáctico. El autómata en este caso se queda sin saber qué acción ejecutar (la entrada de la tabla acción no está definida para el estado actual y el terminal de entrada). Las definiciones precedentes son aplicables a todas las técnicas de análisis que hay esta el momento de la escritura de este trabajo. En el contexto de las técnicas LR(k), un error semántico puede ser ignorado sin que se produzcan efectos indeseados. No ocurre lo mismo con los errores lexicográficos y sintácticos (nadie sabe con certeza lo que quiso escribir el autor del texto fuente), aunque los efectos colaterales pueden reducirse previo estudio y modificación "a mano" de las tablas. 4.3 Fundamentos de la construcción de las tablas de análisis SLR Antes de explicar la construcción de las tablas se darán primero algunas definiciones, se explicarán algoritmos usados por el algoritmo de construcción de las tablas de análisis sintáctico SLR y se explicarán los fundamentos de la construcción de las tablas del analizador SLR. 4.3.1 Elemento del análisis sintáctico LR(0) Se denominará elemento del análisis sintáctico LR(0) (elemento para abreviar) de una gramática G a una regla de G con un punto en alguna posición de la parte derecha. 33 Algoritmos usados por el Generador de Analizadores Sintácticos Una regla que derive en la cadena nula (A --> ) genera un solo elemento que es "A --> . ". Intuitivamente, un elemento indica hasta dónde se ha visto una producción en un momento dado del proceso de análisis sintáctico. Por ejemplo, la regla A --> B C D genera cuatro elementos: A A A A ---> ---> ---> ---> . B B B B . C C C C . D D D D . El primer elemento indica que a continuación se espera ver en la entrada una cadena derivable de "B C D". El segundo elemento indica que se acaba de ver en la entrada una cadena derivable de B, y que a continuación se espera ver una cadena derivable de "C D". 4.3.2 Operación Cerradura Sea I un conjunto de elementos para una gramática G, Cerradura(I) es el conjunto de elementos construidos a partir de I y de G mediante las dos siguientes reglas: 1. Inicialmente, todo elemento de I se agrega a Cerradura(I). 2. Si A --> α . B β está en Cerradura(I) y B --> γ es una regla, entonces se agrega B --> . γ a Cerradura(I), si todavía no fue agregado. Se aplica esta regla hasta que no se puedan agregar más elementos a Cerradura(I). Intuitivamente, si A --> α . B β está en Cerradura(I) es porque en algún momento del proceso de análisis sintáctico se cree que es posible ver a continuación una cadena derivable de B como entrada. Si B --> γ es una regla, también se espera ver una subcadena derivable de B --> γ en éste punto, y por esta razón se incluye B --> . γ en Cerradura(I). Ejemplo: para la gramática utilizada en el punto 4.2.2, sea I = { (P --> • D I) } entonces Cerradura(I) contiene los siguientes elementos: P ---> • D I D ---> • 'Var' L ':' 'Tipo' ';' D ---> • 34 Algoritmos usados por el Generador de Analizadores Sintácticos Otro ejemplo: para la gramática utilizada en el punto 4.2.2, sea I = { ( D ---> 'Var' • L ':' 'Tipo' ';' ) }, entonces Cerradura(I) contiene los siguientes elementos: D ---> 'Var' • L ':' 'Tipo' ';' L ---> • L ',' 'Id' L ---> • 'Id' 4.3.3 Mangos Informalmente, un mango de una cadena es una subcadena que concuerda con el lado derecho de una regla y cuya reducción al no terminal del lado izquierdo de la regla representa un paso a lo largo de la inversa de una derivación por la derecha. Formalmente, un mango de una forma de frase derecha γ es una regla A-->β y una posición de γ donde la cadena β podría encontrarse y sustituirse por A para producir la forma de frase derecha previa en una derivación por la derecha de γ. Es decir, si O ==>* αAω ==>* αβω, entonces A-->β si la posición que sigue de α es un mango de αβω. La cadena ω a la derecha del mango contiene sólo símbolos terminales. Si la gramática no es ambigua entonces toda forma de frase derecha de la gramática tiene exactamente un mango. Para la gramática del ejemplo dado en la sección 4.2.2, obsérvesen la figura 12, la fila correspondiente al paso 14: la forma de frase derecha es D I Instr ; la operación que se va a ejecutar en ese paso es r6 y el mango es "I Instr ;", que concuerda con la parte derecha de la regla 6 ( I --> I 'Instr' ';' ). En este caso, a la derecha del mango no hay símbolos terminales. 4.3.4 Prefijo Viable Los prefijos de las formas de frase derecha que pueden aparecer en la pila de un analizador sintáctico por desplazamiento y reducción se denominan prefijos viables. Una definición equivalente de un prefijo viable es la de que es un prefijo de una forma de frase derecha que no continúa más allá del extremo derecho del mango situado más a la derecha de esta forma de frase. Con esta 35 Algoritmos usados por el Generador de Analizadores Sintácticos definición, siempre es posible añadir símbolos terminales al final de un prefijo viable para obtener una forma de frase derecha. Por lo tanto, aparentemente no hay error siempre que la porción examinada de la entrada hasta un punto dado pueda reducirse a un prefijo viable. Por ejemplo, para la gramática del ejemplo dado en la sección 4.2.2, obsérvese en la figura 12, la fila correspondiente al paso 8, la forma de frase derecha es: Var L : Tipo ; Instr ; y se reduce por regla 2 el mango "Var L : Tipo ;" para obtener la forma de frase derecha siguiente: D Instr ; Aquí, D es un prefijo viable. 4.3.5 Operación ir_a La función ir_a(I, X) donde I es un conjunto de elementos y X es un símbolo de la gramática (terminal o no terminal) se define como la cerradura del conjunto de todos los elementos A --> α X . β tales que A --> α . X β esté en I. Intuitivamente, si I es el conjunto de elementos válidos para algún prefijo viable, entonces ir_a(I, X) es el conjunto de elementos válidos para el prefijo viable γX. Por ejemplo, para la gramática del ejemplo dado en la sección 4.2.2, para el conjunto de elementos I igual a: P ---> • D I D ---> • 'Var' L ':' 'Tipo' ';' D ---> • entonces ir_a(I, D) consta de: P ---> D • I I ---> • I 'Instr' ';' I ---> • 36 Algoritmos usados por el Generador de Analizadores Sintácticos 4.3.6 Colección Canónica de Conjuntos de Elementos del Análisis Sintáctico LR(0) La construcción de la colección canónica de conjunto de elementos LR(0) se aplica a una gramática aumentada G' obtenida a partir de G por el agregado de la siguiente regla: O' --> O donde O es el símbolo inicial de la gramática G. Ahora O' es el símbolo inicial de la gramática G', a la que se llamará gramática aumentada. El objetivo de esta regla es la de indicar al analizador cuándo debe detener el análisis sintáctico y anunciar la aceptación de la cadena. La aceptación se produce únicamente cuando el analizador está por reducir O' --> O. El algoritmo se muestra en la siguiente figura, en donde C es conjunto de conjuntos de elementos (la colección canónica LR(0) ): Sea C = { Cerradura( { O' --> . O } ) } Repetir Para cada conjunto de elementos I en C y cada símbolo gramatical X tal que ir_a(I, X) no esté vacío y no esté en C hacer Agregar ir_a(I, X) a C. FinPara Hasta que no se puedan agregar más conjuntos de elementos a C. Fig. 13 Construcción de la Colección Canónica LR(0) En adelante, a cada I se lo llamará indistintamente conjunto de elementos LR(0) o estado. Ejemplo: para la gramática dada en la sección 4.2.2, la gramática aumentada es: P' --> P ---> D ---> D ---> L ---> L ---> I ---> I ---> P D I 'Var' L ':' 'Tipo' ';' L ',' 'Id' 'Id' I 'Instr' ';' 37 Algoritmos usados por el Generador de Analizadores Sintácticos y la colección canónica es: Estado 0 (I0): P' ---> • P P ---> • D I D ---> • 'Var' L ':' 'Tipo' ';' D ---> • Estado D ---> L ---> L ---> 1 (I1): 'Var' • L ':' 'Tipo' ';' • L ',' 'Id' • 'Id' Estado 2 (I2): P' ---> P • Estado P ---> I ---> I ---> 3: D • I • I 'Instr' ';' • Estado 4: L ---> 'Id' • Estado 5: D ---> 'Var' L • ':' 'Tipo' ';' L ---> L • ',' 'Id' Estado 6: P ---> D I • I ---> I • 'Instr' ';' Estado 7: D ---> 'Var' L ':' • 'Tipo' ';' Estado 8: L ---> L ',' • 'Id' Estado 9: I ---> I 'Instr' • ';' Estado 10: D ---> 'Var' L ':' 'Tipo' • ';' Estado 11: L ---> L ',' 'Id' • Estado 12: I ---> I 'Instr' ';' • Estado 13: D ---> 'Var' L ':' 'Tipo' ';' • 4.3.7 Idea Central del Método SLR Los elementos del análisis sintáctico LR(0) pueden considerarse como los estados del autómata finito no determinista que reconoce los prefijos viables. Para poder convertir el autómata en determinista hace falta agrupar los 38 Algoritmos usados por el Generador de Analizadores Sintácticos elementos en conjuntos. Los conjuntos serían entonces los estados del autómata finito determinista que reconoce los prefijos viables. La idea central del método SLR es construir primero a partir de la gramática un AFD que reconozca los prefijos viables. Se construye la colección canónica de elementos LR(0). Cada conjunto de elementos dentro de la colección canónica pasan a ser los estados del analizador sintáctico SLR (del AFD que el mismo usa). Para el ejemplo dado en el punto anterior, el AFD que reconoce prefijos viables se muestra en la figura siguiente: 'Id' ar' 'V 0 P 1 4 L ':' 5 2 7 ',' D 'Tipo' 'Id' 8 3 I 6 'Instr' 9 ';' 10 ';' 13 11 12 Fig. 14 AFD que reconoce prefijos viables de la gramática del ejemplo. El AFD de la figura precedente tiene dos problemas: no tiene estados finales y tiene transiciones con símbolos no terminales. El hecho de tener transiciones con no terminales hace necesario disponer de una pila para la implementación del autómata. El autómata resultante se pasa a llamar autómata de pila, que como ya se mencionó en el punto 2.2.5, son los que implementan analizadores sintácticos para gramáticas del Tipo 2 de la clasificación de Chomsky. 4.3.8 Función Anulable La función Anulable se aplica a un símbolo no terminal de una gramática y devuelve Verdadero si ese símbolo es anulable. Un símbolo no terminal A es anulable si de alguna forma, mediante derivaciones sucesivas con las reglas de la gramática, se transforma A en la 39 Algoritmos usados por el Generador de Analizadores Sintácticos cadena nula ( A ==>* λ ). (λ representa a la cadena nula. En este trabajo cuando en una regla se quiere denotar la presencia de la cadena nula no se pone nada puesto que es más claro.) Definiremos primero una versión simple de Anulable a la que se le llamará AnulableSimple. Esta función devolverá Verdadero si hay una regla que derive el no terminal directamente en la cadena nula ( A --> ). A partir de la función de AnulableSimple se define la función Anulable de la siguiente manera: un símbolo no terminal X de una gramática G es anulable si: 1. X es Anulable Simple ( X --> ). 2. Si existe alguna regla X --> Y Z, con Y y Z anulables. El punto 2 hace que la definición sea recursiva, por lo tanto un algoritmo que la implemente podrá ser recursivo, lo que no es aconsejable debido a la cantidad de veces que se puede repetir el cálculo de Anulable para algunos símbolos no terminales. Ejemplo: para la gramática siguiente O A A B B C C ---> ---> ---> ---> ---> ---> ---> A B C a B b C C c O El cálculo de AnulableSimple para cada uno de los símbolos daría el siguiente resultado: O: A: B: C: no si no si es es es es anulable anulable anulable anulable simple. simple. simple. simple. El cálculo de Anulable arroja el siguiente resultado: O: A: B: C: si si si si es es es es anulable anulable anulable anulable porque porque porque porque A, B y C son anulables. es anulable simple. hay una regla B --> C, y C es anulable. es anulable simple. 4.3.9 La operación Primero Si α es una cadena de símbolos gramaticales, se considera Primero(α) como el conjunto de terminales que inician las cadenas derivadas de α. 40 Algoritmos usados por el Generador de Analizadores Sintácticos Si α ==>* λ, entonces λ también está en Primero(α). El resultado de la función Primero es un conjunto de símbolos terminales, que obviamente es un subconjunto de T (el conjunto de símbolos terminales de la gramática). Para calcular Primero(X) para todos los símbolos gramaticales X, se proponen las siguientes reglas: 1. Si X es terminal, entonces Primero(X) es { X }. 2. Si X -- > λ es una regla, entonces agregar λ a Primero(X). 3. Si X es no terminal y X --> Y1 Y2 ... Yk entonces se agrega b a Primero(X) si, para alguna i, b está en Primero(Yi) y λ está en todos los conjuntos Primero(Y1), ..., Primero(Yi - 1); esto es, la cadena Y1...Yi - 1 ==>* λ. Si λ está en Primero(Yj) para toda j = 1, 2, ..., k, entonces se agrega λ a Primero(X); esto es, la cadena Y1...Yk ==>* λ. Por ejemplo, todo lo que está en Primero(Y1) sin duda estará en Primero(X). Si Y1 no deriva en λ, entonces no se agrega nada más a Primero(X), pero si Y1 ==>* λ, entonces se agrega Primero(Y2), y así sucesivamente. 4. Se repetirán las tres primeras reglas hasta que no se puedan agregar Fig. 15 Cálculo de Primero(X). Observando la regla 1, se concluye que el cálculo de Primero(X) para X terminal no tiene sentido, puesto que es un axioma. A los efectos de la utilización de la función Primero en la generación de las tablas de análisis sintáctico SLR, el agregado de λ no es necesario. En los ejemplos siguientes no se incluye λ en donde se debiera. Ejemplo 1: para la gramática dada en la sección anterior (4.3.8) los conjuntos Primero sin Lambda (λ) son: Primero(O) Primero(A) Primero(B) Primero(C) = = = = { { { { a a b c b c } } c } } 41 Algoritmos usados por el Generador de Analizadores Sintácticos Ejemplo 2: para la gramática dada en la sección (4.2.2) los conjuntos Primero sin Lambda (λ) son: Primero(P) Primero(D) Primero(L) Primero(I) = = = = { { { { 'Var' 'Instr' } 'Var' } 'Id' } 'Instr' } 4.3.10 La operación Siguiente La función Siguiente se calcula para los símbolos no terminales de la gramática, únicamente. Para calcular Siguiente(X) para todos los símbolos no terminales X, se proponen las siguientes reglas: 1. Agregar FA (Fin de Archivo) a Siguiente(O), donde O es el símbolo inicial y FA es el delimitador derecho de la entrada. 2. Si hay una regla A ---> α B β, entonces todo lo que esté en Primero(β) excepto λ se agrega a Siguiente(B). 3. Si hay una regla A ---> α B o una regla A ---> α B β, donde Primero(β) contenga λ (es decir, β ==>* λ), entonces todo lo que esté en Siguiente(A) se agrega a Siguiente(B). Fig. 16 Cálculo de Siguiente(X). Obsérvese las reglas 2 y 3, al decir Primero(β) se está hablando en realidad de la unión de varios conjuntos Primero(Xi) para i = 1, ..., k y todos los Primero(Xi) excepto Primero(Xk) contiene a λ. Por lo tanto, se deberá tener esto en cuenta al implementar el algoritmo. Obsérvese también que en la regla 2, al agregar todo lo que esté en Primero(β) excepto λ a Siguiente(B) justifica el hecho de que en este trabajo se calcula Primero(X) sin λ. Ejemplo 1: para la gramática dada en la sección anterior (4.3.8) los conjuntos Siguiente(X) son los siguientes: Siguiente(O) = { c b } Siguiente(A) = { b c } 42 Algoritmos usados por el Generador de Analizadores Sintácticos Siguiente(B) = { c b } Siguiente(C) = { c b } Ejemplo 2: para la gramática dada en la sección (4.2.2) los conjuntos Siguiente(X) son: Siguiente(P) Siguiente(D) Siguiente(L) Siguiente(I) 4.4 = = = = { { { { } 'Instr' } ':' ',' } 'Instr' } Construcción de las tablas de análisis SLR La construcción de las tablas de análisis sintáctico SLR (similar a la mostrada en la figura 11 de la sección 4.2.2) se resume en el algoritmo mostrado en la figura siguiente. Si los 3 primeros pasos generan acciones contradictorias, se dice que la gramática no es SLR(1) porque el algoritmo no consigue producir las tablas para el análisis sintáctico. En este caso no se ejecutan los pasos siguientes. 43 Algoritmos usados por el Generador de Analizadores Sintácticos 1. Construir G', la gramática aumentada, a partir de G, por el agregado de la regla O' --> O, donde O era el símbolo inicial de G. O' pasa a ser el símbolo inicial en G'. 2. Construir C = { I0, I1, ..., In }, la colección canónica de conjuntos de elementos del análisis sintáctico LR(0) para la gramática aumentada G'. 3. El estado i se construye a partir de Ii. Las acciones de análisis sintáctico para el estado i se determinan como sigue: a. Si A --> α . x β está en Ii con x terminal e ir_a(Ii, x) = Ij, entonces asignar "desplazar j" a accion[i, x]. b. Si A --> α . está en Ii, con A distinto de O' entonces asignar "reducir por A --> α" a accion[i, x] para todo x perteneciente a Siguiente(A). c. Si O' ---> O . está en Ii, entonces asignar "aceptar" a accion[i, FA] (FA es el Fin de Archivo). 4. La tabla ir_a(i, X) para el estado i y el no terminal X se construyen utilizando la regla: si ir_a[Ii, X] = Ij entonces ir_a[i, X] = j 5. Todas las entradas de las tablas no definidas por las reglas 3 y 4 son consideradas "error". 6. El estado inicial del analizador es el que se construye a partir del conjunto de elementos que contiene a S' ---> . S Fig. 17 Construcción de las tablas de análisis sintáctico SLR. Ejemplo 1: para la gramática dada en la sección 4.2.2, las tablas se muestran en la figura 12 que se encuentra en la misma sección. La colección canónica de elementos LR(0) los encontrará en la sección 4.3.6. Los conjuntos Siguiente(X) los encontrará en la sección 4.3.10. Ejemplo 2: para la gramática dada en la sección 4.3.8, los conjuntos Siguiente(X) los encontrará en la sección 4.3.10. La colección canónica de 44 Algoritmos usados por el Generador de Analizadores Sintácticos elementos LR(0) es la siguiente (aquí a los terminales se los encierra entre comillas simples para que no queden dudas): Estado 0: O' ---> • O O ---> • A B C A ---> • 'a' B A ---> • Estado A ---> B ---> B ---> C ---> C ---> 1: 'a' • B • 'b' C • C • 'c' O • Estado 2: O' ---> O • Estado O ---> B ---> B ---> C ---> C ---> 3: A • B C • 'b' C • C • 'c' O • Estado B ---> C ---> C ---> 4: 'b' • C • 'c' O • Estado C ---> O ---> A ---> A ---> 5: 'c' • O • A B C • 'a' B • Estado 6: A ---> 'a' B • Estado 7: B ---> C • Estado O ---> C ---> C ---> 8: A B • C • 'c' O • Estado 9: B ---> 'b' C • Estado 10: C ---> 'c' O • Estado 11: O ---> A B C • Al construir la tabla acción obtenemos lo siguiente, en la que se detectará 6 conflictos de desplazamiento-reducción: acción(e, x) 'a' 'b' 'c' ir_a(e, X): FA O A B C 45 Algoritmos usados por el Generador de Analizadores Sintácticos 0 1 2 3 4 5 6 7 8 9 10 11 d1 · · · · d1 · · · · · · r3 d4 · d4 r7 r3 r2 r5 r7 r4 r6 r1 r3 d5 · d5 d5 r3 r2 r5 d5 r4 r6 r1 r3 r7 a r7 r7 r3 r2 r5 r7 r4 r6 r1 2 · · · · 10 · · · · · · 3 · · · · 3 · · · · · · · · 6 7 · · 8 7 · 9 · · · · · · · 11 · · · · · · Los conflictos los generó el paso 3.b del algoritmo y son los siguientes: 1. En el estado 1, con el terminal 'c' hay que elegir entre d5 o r7. 2. En el estado 1, con el terminal 'b' hay que elegir entre d4 o r7. 3. En el estado 3, con el terminal 'c' hay que elegir entre d5 o r7. 4. En el estado 3, con el terminal 'b' hay que elegir entre d4 o r7. 5. En el estado 4, con el terminal 'c' hay que elegir entre d5 o r7. 6. En el estado 8, con el terminal 'c' hay que elegir entre d5 o r7. Debido a que esta gramática genera conflictos en la construcción de las tablas SLR se dirá que la misma no es SLR. Una definición equivalente es decir que la gramática es ambigua para la técnica de análisis SLR, lo que no significa que sea ambigua a secas (la construcción unívoca de un árbol de análisis sintáctico no es posible con la técnica SLR, esto es, el algoritmo de construcción de las tablas se comporta en este caso como una función no inyectiva porque puede generar varias tablas accion alternativas para la misma gramática). Más ejemplos de construcción de tablas las puede encontrar en el capítulo 11 y 12 en donde se muestran ejemplos de ejecución de SLR1. 46