E N D
Analizador Sintáctico Shift-reduce parser La técnica del shift-reduce parser es utilizada en varios generadores automáticos de parsers. El SR-parsing intenta construir un árbol sintáctico (parse tree) para una cadena de entrada, a partir de los elementos terminales y por eso es llamado un parser del tipo Bottom-up. El proceso consiste en reducir una cadena de entrada w hasta alcanzar el símbolo inicial de la gramática.
Shift-Reduce parser Definiciones Básicas Def. Llamamos handle de una forma sentencial derecha , consistente en una producción A , y una posición de en donde la cadena puede ser localizada y remplazada por A para producir la forma sentencial derecha previa, en una derivación derecha de . Esto es, si: S *Aw w con wVt* entonces A en la posición siguiente a es el handle de w. Si la gramática no es ambigüa entonces toda forma sentencial derecha tiene exactamente un handle.
Shift-Reduce parser Laderivación de más a la derecha en reversa, frecuentemente es llamada secuencia reductiva canónica, y es obtenida al podar el handle. Existen dos problemas para implementar el handle: (1) Como localizar el handle para podarlo. (2) Que producción elegir si existen más de una regla con el mismo lado derecho.
Shift-Reduce parser Un método importante para implementar el Shift-Reduce parsing, es mediante la utilización de una pila y un buffer de entrada. Podemos utilizar el símbolo $ para marcar el fondo de la pila y el final de más a la derecha del buffer. Ejemplo: Pila Buffer $ el perro ladra $ El parser opera desplazando cero o más símbolos de entrada a la pila, hasta que un handle aparece en el tope de la misma. Entonces el parser reduce a el lado derecho de la producción correspondiente (ej. A ).
Shift-Reduce parser El parser repite este ciclo hasta que algún error es detectado o la pila contiene el símbolo inicial y el símbolo $ en el buffer de entrada: Pila Buffer $ S $ Si al final se tiene la configuración mostrada, el parser termina indicando ACCEPT. Existe un hecho que justifica la utilización de la pila: el handle siempre aparecerá eventualmente en el tope de la pila, nunca dentro.
Shift-Reduce parser Ejemplo: Pila Buffer Acción
Shift-Reduce parser Operaciones Básicas • Shift – El siguiente símbolo de la entrada es desplazado al tope de la pila. • Reduce – El parser identifica que la parte derecha del handle está en el tope de la pila. Debe entonces localizar la parte izquierda del handel dentro de la pila y decidir que no terminal sustituir por el handle. • Accept – El parser anuncia haber completado exitosamente el reconocimiento sintáctico. • Error – El parser identifica que un error de sintaxis ha sido detectado y llama a la rutina de recuperación de errores.
LR-parser Ahora tenemos lo necesario para construir parsers muy eficientes, para una clase grande de gramáticas libres de contexto CFGs. Estos parsers son llamados LR, dado que reconocen la entrada de izquierda-a-derecha y construyen una right-most derivation en reversa. Ventajas • Se pueden construir para reconocer virtualmente toda construcción en un lenguaje de programación, para el cual una CFG puede ser escrita. • El método LR es más general que otros métodos de parsing y muy eficiente. • Es posible detectar errores de sintaxis casi al momento. Desventajas • Muy difícil de implementar desde cero.
Generación del LR-parser El LR-parser consiste de dos partes: • Una rutina controladora • La tabla de parsing Dado que la rutina controladora es siempre la misma para todo LR-parser, el problema principal es generar la tabla de parsing. Tabla de parsing Generador de la Tabla Gramática
Generación del LR-parser Operación del LR-parser: Buffer de Entrada Pila Rutina Controladora Tabla de Parsing
Generación del LR-parser Existen muchos métodos para generar la tabla de parsing. El método más simple de implementar es el llamado Simple LR-parser (SLR), que se introducirá más adelante. Como se mostró en el diagrama anterior existen tres estructuras importantes: la pila, el buffer de entrada y la tabla de parsing. La entrada se lee de izquierda-a-derecha, un símbolo a la vez. La pila contiene cadenas de la forma: s0 X1 s1 X2 s2 … Xm sm en donde sm está en el tope de la pila. Cada Xi es un símbolo de la gramática y cada si es un símbolo llamado ESTADO. Cada símbolo de ESTADO sumariza la información contenida bajo él, en la pila, y es utilizado para guiar la decisión de shift-reduce.
Algoritmo del LR-parser La tabla de parsing consiste de dos partes: • La función de acción del parser, llamada ACTION. • La función GOTO. El programa controlador del LR-parser es siempre el mismo y opera de la siguiente manera: • Determina el estado localizado en el tope de la pila, sm, y el símbolo de entrada ai en proceso. • Consulta ACTION(sm , ai ). La entrada en la tabla de parsing que contiene la acción a ejecutar cuando éste se encuentra en el estado sm, para una entrada ai. La entrada ACTION(sm , ai ). puede tener uno de cuatro valores: (1) Shift s (2) Reduce A (3) Accept (4) Error
Algoritmo del LR-parser Modelo de un LR Parser Pila Buffer LR Parsing Program actiongoto
Algoritmo del LR-parser • La función goto(sk, ai ) de la tabla de parsing en una función de transición de estados. Toma un estado y un símbolo y genera otro estado. Esta función reconoce los prefijos viables de la gramática G. Una configuración de un LR Parser es un par cuyo primer componente es el contenido del stack y el segundo el resto de la entrada: (s0 X1 s1 X2 s2 … Xm sm, ai ai+1 … an$) Esta configuración representa la forma sentencial derecha: X1 X2 … Xm ai ai+1 … an
Algoritmo del LR-parser Inicializa ip a la posición del primer símbolo de w$ Repetir Sea s el estado en el tope de la pila y a el símbolo apuntado por el ip. Si action[s, a] = shift s´ shift a y s´ al tope de la pila incrementa ip apuntando al siguiente símbolo fin sino Si action[s, a] = reduceA pop 2*| | símbolos de la pila supongamos que s´ es el estado en el tope de la pila push A y coloca goto[s´, A] en el tope de la pila imprime la producción A fin sino Si action[s, a] = accept return sino error() fin hasta FALSE
Algoritmo del LR-parser Ejemplo: Muestre las funciones de action y goto de una LE parsing table para la gramática de expresiones matemáticas que se muestra a continuación: (1) E E + T (2) E T (3) T T * F (4) T F (5) F (E) (6) F id Exp: id * id + id
Construcción de la tabla de LR parsing Una gramática para la cual podemos construir una tabla de parsing, llamada gramática LR, es aquella en la cual la entrada está unívocamente definida. No todas las gramáticas libres de contexto CFGs, son gramáticas LR. Sin embargo, es factible evitar aquellas que no lo son cuando se definen construcciones típicas de un lenguaje de programación. Algunos problemas: • Una gramática ambigua no puede ser una gramática LR. • Otro problema es cuando al tener un handle, más de una regla puede ser aplicada. El método mostrado a continuación es llamado el “simple LR” o SLR. .
Construcción de la tabla de LR parsing Items LR(0) Los ítems LR(0) o ítems canónicos para una gramática G, consisten en una producción de G con un punto en algún lugar de su lado derecho. Ej. A X Y Z genera 4 items: A . X Y Z A X .Y Z A X Y. Z A X Y Z . La producción A genera solo el item: A . Intuitivamente, un ítem indica que tanto de la producción hemos identificado en un punto determinado del proceso de parsing. Estos ítems se agrupan formando conjuntos que constituyen estados de un autómata finito determinístico AFN, reconociendo prefijos viables de una producción.
Construcción de la tabla de LR parsing Los llamados ítems canónicos, o la colección LR(0), proveen la base para la construcción de aquella clase de parsers LR, llamados LR simples o SLR. Para la construcción de esta colección canónica para una gramática G dada, se necesitan: • Una gramática aumentada • Dos funciones: CLOSURE y GOTO Def. Sea G una gramática libre de contexto, con símbolo inicial S. Llamamos gramática aumentada G´ al resultado de agregar a G, el símbolo S´ y la producción S´ S.
Construcción de la tabla de LR parsing CERRADURA (closure) Si I es un conjunto de ítems para una gramática G, entonces el conjunto de ítems llamado cerradura(I) se construye a partir de I a través de las reglas: (i) Para todo i I, i cerradura(I) (ii) Si A . Besta en la cerradura(I), y B es una producción, entonces agregar el ítem B . a la cerradura de I, si no se encuentra ya adentro. Esta regla se aplica hasta que ya no se puedan agregar más items a la cerradura(I).
Construcción de la tabla de LR parsing Ej. Sea la siguiente gramática aumentada: E´ E E E + T | T T T * F | F F ( E ) | id Si I es un conjunto que contiene un solo ítem {E´ . E}, entonces la cerradura(I) contiene los ítems: E´ . E E . E + T E . T T . T * F T . F F . ( E ) F . id
Construcción de la tabla de LR parsing GOTO La función Ii=GOTO( I, X), en donde I es el conjunto de ítems, X es un símbolo de la gramática, y Ii es el conjunto resultado de aplicar la cerradura a los ítems [A X . ] tales que [A. X ] es en I. Ej. Si I es el conjunto de ítems: { E´E ., E E . + T} Entonces GOTO(I, +) consiste en: E E + . T T . T * F T . F F . ( E ) F . id
Construcción de la tabla de LR parsing Procedimiento de construcción de conjuntos de ítems LR(0) Procedure ITEMS(G´) Begin C= {cerradura({S´ . S})} Repeat Para cada conjunto de ítems Ii en C y para cada símbolo X, tal que GOTO(Ii, X) no está vacío y no se encuentra en C, haz: Agrega GOTO(Ii, X) a C. until NO se puedan agregar más conjuntos a C. end
Construcción de la tabla de LR parsing Ejemplo: La colección de conjuntos canónicos de ítems de LR(0) para la gramática del ejemplo anterior: I0: E´ . E I1: E´ E . I5: F id . I9: E E + T . E . E + T E E . + T T T . * F E . T I6: E E + . T T . T * F I2: E T . T . T * F I10: T T * F . T . F T T . * F T . F F . ( E ) F . ( E ) I11: F ( E ) . F . id I3: T F . F . id I4: F ( . E ) I7: T T * . F E . E + T F . ( E ) E . T F . id T . T * F T . F I8: F ( E . ) F . ( E ) E E . + T F . id
Construcción de la tabla de LR parsing El algoritmo nos genera la siguiente tabla:
Construcción de la tabla de LR parsing Los conjuntos canónicos para LR(0) generados por la función GOTO se presentan como un diagrama de transición de un Autómata Finito Determinístico. id id I7 * I0 I5 id id * F T I10 I2 E T T I9 I1 F F I3 “)” I11 + F I6 + “(“ I4 “(“ I8 E “(“ “(“
Construcción de la tabla de LR parsing Cada estado de la figura anterior es un estado final e I0 es el estado inicial. Entonces el AFD M reconoce exactamente los prefijos viables de la gramática original. Para cualquier gramática G, la función GOTO de la colección canónica de conjuntos de ítems define un autómata finito determinístico que reconoce los prefijos viables de G. Existe una transición de A. X a AX. etiquetada X, y existe una transición de A.B a B. etiquetada . Entonces, la cerradura(I) para conjuntos de ítems I (estados de M), es exactamente la cerradura- del conjunto de estados del autómata finito no determinístico definido.
Construcción de la tabla de LR parsing Def. Decimos que el ítem A 1.2 es válido para un prefijo viable1, si existe una derivación: S´==>A==> 12 En general un ítem será válido para varios prefijos viables. El hecho de que A1.2 sea válido para 1,nos dice mucho con respecto a si debemos aplicar un shift o un reduce cuando encontremos 1 en la pila de parsing: (i) si 2 ≠ entonces 2 aún no ha sido procesado y la acción será un shift. (ii) si 2 = entonces existe una regla A1 y debemos aplicar un reduce con esta producción.
Construcción de la tabla de LR parsing Teorema. El conjunto de ítems validos para cada prefijo viable , es exactamente el conjunto de ítems alcanzado desde el estado inicial, a lo largo de la ruta etiquetada en el AFD construido a partir de la colección canónica de conjuntos de ítems con transiciones dadas por GOTO. Ejemplo: Mostrar que la cadena E+T* que nos lleva al estado I7 del AFD desarrollado, es un prefijo viable para los ítems definidos en ese estado: I7: T T * . F F . ( E ) F . id
Construcción de la tabla de LR parsing FIRST y FOLLOW Existen dos funciones asociadas con una gramática G, que ayudan a la construcción de un parser predictivo. Def. Sea una cadena cualquiera de símbolos de la gramática G, definamos FIRST() como el conjunto de terminales que inician las cadenas derivadas de . Si ==>* , entonces la palabra vacía, , también está en FIRST(). Def. Para un noterminal A, definimos FOLLOW(A), como el conjunto de terminales a, tal que pueden aparecer inmediatamente a la derecha de A en alguna forma sentencial. Es decir, el conjunto de terminales a, tal que existe una derivación de la forma S==>* Aa. Si A puede ser el símbolo de más a la derecha de alguna sentencia, entonces $ esta en FOLLOW(A).
Construcción de la tabla de LR parsing Para evaluar FIRST(X) para todos los símbolos X de la gramática, aplique las siguientes reglas hasta que ningún otro terminal o la palabra vacía pueda ser agregado a FIRST(X): • Si X es un terminal, entonces haz FIRST(X) = {X}. • Si X es una producción, entonces agrega a FIRST(X). • Si X es un noterminal y XY1Y2…Yk es una producción, entonces • Coloque a en FIRST(X) si para algún i, a es en FIRST(Yi), y ocurre en todos los FIRST(Y1),…FIRST(Yi-1); esto es, Y1 ==>* …Yi-1==>* . • Si is in FIRST(Yj), para todos los j=1,2,…,k, entonces agregue a FIRST(X). Esto es, todo lo que está en FIRST(Y1), está en FIRST(X), pero si FIRST(Y1) contiene , entonces agrege el contenido de FIRST(Y2), y así sucesivamente.
Construcción de la tabla de LR parsing Computemos ahora FIRST() en donde es una cadena de símbolos X1X2…Xn • Agregue a FIRST(X1X2…Xn) todos los símbolos distintos de en FIRST(X1). • Agregue también todos los símbolos no- en FIRST(X2); sólo si está en FIRST(X1). • Agrege todos los símbolos no- de FIRST(X3); sólo si está en FIRST(X2) y está en FIRST(X1) y continúe así sucesivamente. • Finalmente, agregue el símbolo a FIRST(X1X2…Xn) si, para todos los i´s, FIRST(Xi) contiene a .
Construcción de la tabla de LR parsing Para evaluar FOLLOW(A) para todos los noterminals A, aplique las siguientes reglas hasta que nada más pueda ser agregado a ningún conjunto FOLLOW: • Coloca $ en Follow(S), en donde S es el símbolo inicial y $ la marca de fin del buffer de entrada. • Si existe una producción A B, entonces todo lo que se encuentre en FIRST() excepto , se coloca en FOLLOW(B). • Si existe una producción A B, o una producción A B en donde FIRST() contiene (es decir, ==>* ), entonces todo lo que se encuentra en FOLLOW(A) está en FOLLOW(B).
Construcción de la tabla de LR parsing Ejemplo: Genere los conjuntos FIRST(X) y FOLLOW(Y) para todos los símbolos noterminales X y Y, de la gramática cuyas producciones se muestran a continuación: ETE’ E’ +TE’| TFT’ T’*FT’| F(E) | id
Construcción de la tabla de LR parsing Solución: FIRST(E)=FIRST(T)=FIRST(F)={(,id} FIRST(E’) = {+, } FIRST(T’) = {*, } FOLLOW(E)=FOLLOW(E’)={),$} FOLLOW(T)=FOLLOW(T’)={+,),$} FOLLOW(F) = {+,*,),$}
Tablas de parsing SLR Algoritmo:Construcción de la tabla SLR Input: Una gramática aumentada. Output: Las funciones acción y goto para la tabla SLR para G´. Método: 1. Construya C={I0, I1, …, In}, la colección de conjuntos de ítems de LR(0) para G´. 2. El estado i se construye a partir de Ii. Las acciones de parsing para el estado i se determinan de la siguiente manera: • Si [A . a ] esta en Ii, y goto(Ii, a) = Ij, entonces fija acción[i, a] como “shift j”. Es requisito que a sea un terminal. • Si [A .] esta en Ii, entonces fija acción[i, a] como “reduce A ” para toda a en FOLLOW(A); en donde A no puede ser S´. • Si [S´S .] es en Ii, entonces fija acción[i, $] como “aceptar” Si algún conflicto de acciones se presenta, decimos que la gramática no es una gramática SLR(1) y el algorítmo falla en producir el parser.
Tablas de parsing SLR Algoritmo:Construcción de la tabla SLR (cont. ) 3. Las transiciones GOTO para el estado i se construyen para todos los símbolos no terminales A, utilizando la regla: si GOTO(Ii, A)=Ij, entonces GOTO[i, A]=j. 4. Todas las entradas no definidas por las reglas 2 y 3, se marcan como “error”. 5. El estado inicial del parser es aquel constituido por el conjunto de ítems conteniendo [S´ S].