Definición: Una Gramática Libre de Contexto (GLC) está en Forma Normal de Greibach (FNG) si todas las producciones son de la forma: A ab 1 B 2.. B k Donde A es un símbolo no Terminal, a es un símbolo Terminal y B 1 B 2.. B k son símbolos no Terminales. Además tiene la característica de: Ai Aj solo si j > i Ejemplo de FNG: S asb ab B b Ejemplo que NO es FNG: S asb ab A abb B b A Notar que las gramáticas utilizadas son sin recursión por la izquierda: S asb ab A abb B b A Paso 1.- Ordenamos los no terminales alfabéticamente A<B<S dando como resultado la siguiente gramática ordenada. A abb B b A S asb ab Paso 2.-Revisamos que las producciones no tengan problemas de precedencia, es decir, en la producción B A, B no debe producir a A debido a que alfabéticamente A está antes de B, por lo tanto estas reglas no cumplen la FNG. Para solucionar este problema se hace lo siguiente: Considerando que la producción A abb no tienen problemas de precedencia podemos sustituirla en B A quedando la nueva Gramática de la siguiente manera: A abb B b abb S asb ab (Es importante aclarar que la precedencia solo se evalúa para la primera posición de las producciones). Paso 3.- Para este ejercicio en particular, podemos ver que en ninguna de las producciones se tiene el problema de recursión a la izquierda. Por lo que la Gramática sigue teniendo la forma: A abb B b abb S asb ab Paso 4.-Del algoritmo, podemos notar que solo las producciones S asb y B b tienen la FNG pero A abb, B abb y S ab aún tienen un problema, ya que en sus producciones contienen símbolos terminales después de la primera posición, lo cual viola la FNG (A ab 1 B 2.. B k ). Par solucionar esto, nos basamos en crear una producción artificial por cada
símbolo terminal x, que se encuentre después de la primera posición de las producciones que tienen este problema, la forma de las nuevas producciones artificiales será Q x x. Con esto la gramática queda finalmente así: A a Q b B B b a Q b B S asb a Q b Q b b Finalmente notamos que ninguna de las reglas de la última gramática produce a A, ya que fue absorbida en el paso 2 de este ejercicio por B al hacer la sustitución, en este momento podemos eliminar la producción de A ya que queda inalcanzable. Otras formas de ir formateando (o filtrando) la gramática. Remover la left recursion desde la gramática: S Ab a A SAa b. Sol.: Reemplazar A en S Ab a, por el lado derecho de, A SAa b, para obtener S SAab bb a A SAa b. Ahora, elimine la direct left recursion : S bbt at T AabT Λ A SAa b. (Removing indirect left recursion). Considere la siguiente gramática. S Ab a A Sa b. La gramática es left-recursive, por la indirect left recursion S Ab Sab. Para eliminar la indirect left recursion,reemplace A en S Ab por el lado derecho de A Sa b para obtener S Sab bb. La gramática es S Sab bb a. Eliminar la left recursion : S bbt at T abt Λ.
Construcción de los conjuntos FIRS y FOLLOW y su importnacia Recordar que un parser predictivo descendente puede solamente ser construido para una gramática LL(1). Al mismo tiempo, una gramática no es LL(1) si ella es: 1. Left recursiva, o 2. No tiene factorización por la izquierda, o 3. Es ambigua Sin embargo, existen gramáticas que no son left recursiva y son left factored que pueden ser no LL(1). Para ver si una gramática es LL(1), la idea es construirse una parse table para los predictive parser por el método que luego viene, que es un análisis sintáctico predictivo no recursivo. Si notamos que cualquier elemento en la tabla contiene más de una producción (lado derecho) entonces la gramática no es LL(1). Ahora, para construir la tabla se necesita calcular los conjuntos FIRST y FOLLOW para la gramática. Sin embargo, tampoco debemos olvidar que la intuición puede ayudar, tal es el caso de dar una respuesta en el caso de la gramática: S a S b S a b a A A λ a A b A Claramente NO es LL(1) pues el no terminal S, y el lookahead a posee 2 producciones posibles (a S y a b a A). Reglas para First Sets 1. Si X es un terminal entonces First(X) es justamente X. 2. Si existe una producción X ε entonces agregue ε a first(x) 3. Si existe una producción X Y1Y2..Yk entonces agregue first(y1y2..yk) a first(x) 4. First(Y1Y2..Yk) es ya sea 1. First(Y1) (si First(Y1) no contiene ε) 2. o (si First(Y1) contiene ε) entonces First (Y1Y2..Yk) está todo en First(Y1) <excepto para ε > como también en First(Y2..Yk) 3. Si First(Y1) First(Y2)..First(Yk) contienen ε entonces agregue ε a First(Y1Y2..Yk). FIRST(α): devuelve el conjunto de todos los terminales que se pueden encontrar a la cabeza de cualquier derivación de la frase α. Reglas para Follow Sets 1. Primero ingrese $ (el fin de una entrada) en Follow(S) (S es el símbolo de partida) 2. Si existe una producción A abb, (donde a puede ser un string completo) entonces todo en FIRST(b) excepto para ε está en FOLLOW(B). 3. Si existe una producción A ab, entonces todo en FOLLOW(A) está en FOLLOW(B) 4. Si existe una producción A abb, donde FIRST(b) contiene ε, entonces todo en FOLLOW(A) está en FOLLOW(B) FOLLOW(A): devuelve el conjunto de todos los terminales que se pueden encontrar siguiendo a A en cualquier derivación posible.
Ejemplo. 1) E TE' 2) E' +TE' 3) E' ε 4) T FT' 5) T' *FT' 6) T' ε 7) F (E) 8) F id First Sets Veamos, para cada producción. Follow Sets Lo primero es agregar $ al símbolo de partida E. Aplicamos regla 2 a las producciones 6) T' ε y 3) E' ε FIRST(E) = {} FIRST(E') = {ε} FIRST(T) = {} FIRST(T') = {ε} FIRST(F) = {} Aplicamos regla 3 a las producciones 5) T' *FT', esto es, podemos agregar todo lo de First(*FT') en First(T'). Ya que, First(*) usando regla 1 es podemos agregar a First(T'). FIRST(E) = {} FIRST(E') = {+,ε} FIRST(T) = {} FOLLOW(E) = {$} FOLLOW(E') = {} FOLLOW(T) ={} FOLLOW(T') = {} FOLLOW(F) = {} Luego, aplicamos la regla 2 de FOLLOW a la producción 2) E' +TE', lo que permite que todo en First(E') excepto para ε debería estar en Follow(T) FOLLOW(E) = {$} FOLLOW(E') = {} FOLLOW(T) ={+} FOLLOW(T') = {} FOLLOW(F) = {} Luego, aplicamos regla 3 a la producción 1) E TE' esto es, agregar todo Follow(E) en Follow(E')
FIRST(F) = {} FOLLOW(E) = {$} Aplicamos regla 3 a la producción 5) T' FOLLOW(E') = {$} *FT' esta permite que podamos agregar todo FOLLOW(T) ={+} First(*FT') en First(T'). Ya que First(*) usa FOLLOW(T') = {} regla 1 esto es, podemos agregar a First(T') FOLLOW(F) = {} FIRST(E) = {} FIRST(E') = {+,ε} FIRST(T) = {} FIRST(F) = {} Dos producciones más, comienzan con terminales 7) F (E) y 8) F id. Aplicamos la regla 3 para obtener... FIRST(E) = {} FIRST(E') = {+,ε} FIRST(T) = {} FIRST(F) = {'(',id} Se sigue aplicando la regla 3 a la producción 4) T FT', en tal caso, podemos agregar First(FT') a First(T) Ya que First(F) no contiene ε, permite que First(FT') sea justamente First(F) FIRST(E) = {} FIRST(E') = {+, ε} FIRST(T) = { '(', id} FIRST(F) = { '(', id} Finalmente aplicamos la regla 3 a la producción 1) E TE', podemos agregar First(TE') a First(E) Ya que First(T) no contiene ε, esto permite que First(TE') sea justamente First(T) FIRST(E) = { '(', id} FIRST(E') = {+,ε} FIRST(T) = { '(', id} FIRST(F) = { '(', id} Eso es todo. Aplicamos regla 3 a la producción 4) T FT', esto es, agregamos todo Follow(T) en Follow(T') FOLLOW(E) = {$} FOLLOW(E') = {$} FOLLOW(T) ={+} FOLLOW(T') = {+} FOLLOW(F) = {} Aplicamos regla 2 a la producción 5) T' *FT', esto es todo lo que está en First(T') excepto ε estará en Follow(F) FOLLOW(E) = {$} FOLLOW(E') = {$} FOLLOW(T) ={+} FOLLOW(T') = {+} FOLLOW(F) = {*} Aplicamos regla 2 a la producción 7) F (E), esto es, todo First(')') estará en Follow(E) FOLLOW(E) = { $, ) } FOLLOW(E') = {$} FOLLOW(T) ={+} FOLLOW(T') = {+} FOLLOW(F) = {*} Aplicar regla 3 a la producción 1) E TE', esto es, todo lo de Follow(E) está en Follow(E') FOLLOW(E) = {$,)} FOLLOW(E') = {$,)} FOLLOW(T) = {+} FOLLOW(T') = {+} FOLLOW(F) = {*} Aplicamos regla 4 a la producción 2) E' +TE' esto es, todo Follow(E') está en Follow(T) (pues First(E') contiene ε)
Construcción de la Tabla FOLLOW(E) = {$,)} FOLLOW(E') = {$,)} FOLLOW(T) = {+,$,)} FOLLOW(T') = {+} FOLLOW(F) = {*} Aplicamos regla 3 a la producción 4) T FT', esto es, todo Follow(T) está en Follow(T') FOLLOW(E) = {$,)} FOLLOW(E') = {$,)} FOLLOW(T) = {+,$,)} FOLLOW(T') = {+,$,)} FOLLOW(F) = {*} Finalmente, aplicamos regla 4 a la producción 5) T' *FT', esto es, todo Follow(T') está en Follow(F) FOLLOW(E) = {$,)} FOLLOW(E') = {$,)} FOLLOW(T) = {+,$,)} FOLLOW(T') = {+,$,)} FOLLOW(F) = {*,+,$,)} Los analizadores descendentes dirigidos por tabla están constituidos por dos elementos que se utilizan para llevar a cabo el proceso de análisis sintáctico. Una pila, donde se almacenan símbolos gramaticales. Una tabla de doble entrada que representa la gramática. El algoritmo de análisis descendente predictivo para este tipo de analizadores consiste en ir consultando la tabla para saber que regla aplicar y apoyarse en la pila asociada:
Ejemplo. 1) E TE' 2) E' +TE' 3) E' ε 4) T FT' 5) T' *FT' 6) T' ε 7) F (E) 8) F id FIRST(E) = { '(', id} FIRST(E') = {+,ε} FIRST(T) = { '(', id} FIRST(F) = { '(', id} FOLLOW(E) = {$,)} FOLLOW(E') = {$,)} FOLLOW(T) = {+,$,)} FOLLOW(T') = {+,$,)} FOLLOW(F) = {*,+,$,)} La gramática NO es LL(1) si y sólo si, existen más de una entrada para cualquier celda en la tabla.
Left recursion: A veces podemos obtener una gramática LL(k) al remover la left recursion. La idea para direct left recursion: es transformar : A Aw Au Av a b., en A ab bb B wb ub vb Λ. Ejemplo. Remover la left recursion. S Sa b. Sol: S bt, T at Λ. Es LL(1). Left factoring: A veces podemos left-factor una gramática LL(k) para obtener una gramática LL(n) equivalente, donde n < k. Ejemplo. La gramática S aas ab b es LL(2) pero no LL(1). Pero si podemos factorizar sobre un prefijo común a desde las producciones S aas ab, para obtener S at T as b. Esto da la nueva gramática:
S at b T as b. Condiciones para ser LL(1). No ambigua, Factorizada por la izquierda y No recursiva a la izquierda. Decida si es una gramática LL(1), o no. Solución: S AB ٨ A aab ٨ B bb c. - Calculamos los conjuntos FIRST y FOLLOW de las producciones: FIRST(AB) = (FIRST(A) - {٨}) FIRST(B) = {a} {b, c} = {a, b, c}. FIRST(٨) = {٨}, FIRST(aAb) = {a}, FIRST(bB) = {b}, FIRST(c) = {c}. - Calculamos los FOLLOW para los no terminales: FOLLOW(S) = {$}, FOLLOW(A) = {b, c}, FOLLOW(B) = {$}. - La tabla:
Por lo tanto es LL(1), por no tener entradas con definiciones múltiples. Otra forma, más formal, pero basado en los resultados de FIRST y FOLLOW para poder aplicar el análisis predictivo LL(1) es necesario que los conjuntos de predicción de todas las reglas con un mismo antecedente sean disjuntas entre sí. Esto es, A abb {a} A B {b, c} B b {b} B c {c}. Y notamos que {a} {b,c} = Ǿ es LL(1), {b} {c} = Ǿ, es LL(1). Por lo tanto la gramática en total es LL(1). Pero no perdamos la intuición.. Porque la gramática es LL(2) pero no LL(1)? S asa ٨ A abs c. Sol.: Consideremos el string aab. Una derivación parte con S asa. Ahora el lookahead está en la segunda a de la cadena aab, pero en tal caso, tenemos dos opciones para escoger, una de ellas es : S asa y S ٨. Entonces, la gramática no es LL(1). Pero, si consideramos two lookahead letters vemos que el substring ab o ac, se puede lograr con S asa para la siguiente etapa. Analizar si la gramática es LL(1). S asc b C cc d. Por ejemplo, vemos que el string aabcdd tiene la siguiente leftmost derivation, donde cada etapa está unicamente determinada por el actual símbolo lookahead. S asc aascc aabcc aabccc aabcdc aabcdd.