Está en la página 1de 7

“Transformaciones en gramáticas libres de contexto”

Sumario:
1- Introducción
2- Eliminación de símbolos inútiles
3- Gramática libre de e-producciones
4- Eliminación de producciones sencillas
5- Gramática propia
6- Eliminación de recursividad izquierda
7- Forma normal de Chomsky
8- Forma normal de Greibach

1 - Introducción

Dada una gramática en ocasiones es necesario modificarla con el objetivo de simplificarla o de imponerle cierta
estructura a sus reglas de producción sin modificar el Lenguaje generado. En otras ocasiones se realizan
transformaciones en una gramática con el objetivo de modificar el Lenguaje generado, o sea, se obtiene
producto de la transformación una nueva gramática que genera un lenguaje diferente al original. Este tipo de
transformación puede ser útil, por ejemplo, para eliminar la ambigüedad de una gramática, o para obtener la
precedencia adecuada de los operadores cuando se trata de una gramática para generar expresiones
aritméticas.

A continuación se discutirán algunos algoritmos clasificados en el primer grupo, o sea, transformaciones en


gramáticas que no alteran el lenguaje generado.

Estos algoritmos están definidos para gramáticas libres de contexto (GLC), por ser estas las más adecuadas
para la representación de los lenguajes de programación y por tanto la base para la implementación de la fase
de análisis sintáctico de un compilador.

2- Eliminación de símbolos inútiles

Veremos a continuación un algoritmo para eliminar símbolos inútiles en una GLC.

Definición:
Decimos que un símbolo X  N   es inútil en una GLC G = ( N, , P, S ) si no existe una derivación
de la forma:
S * wXy * wxy , donde w, x, y  *

Esta definición, como se puede apreciar, es aplicable tanto a símbolos no terminales como a símbolos
terminales, y por tanto tiene dos interpretaciones distintas. Para el caso de los símbolos no terminales esta
definición nos dice que el símbolo X es inútil si él no es capaz de generar una cadena de símbolos terminales.
Evidentemente un no terminal que no pueda realizar lo anterior es un símbolo inútil dentro de la gramática
debido a que una vez que él aparezca en una forma sentencial, no podrá generarse una sentencia. La siguiente
interpretación de la definición es aplicable tanto a los símbolos no terminales como a los terminales y nos dice
que un símbolo X es inútil si él nunca va a aparecer en alguna forma sentencial, o sea que el símbolo es
inaccesible.

El algoritmo para eliminar símbolos inútiles de una gramática tiene dos partes: una para eliminar los símbolos
no terminales que no generan cadenas de terminales y otra para eliminar los símbolos inaccesibles.

Algoritmo: Eliminación de símbolos inútiles


Entrada: Una GLC G = ( N, , P, S )
Salida: Una GLC G' = ( N', ', P', S ) sin símbolos inútiles que genera el mismo lenguaje que G. Este algoritmo
determina además si el lenguaje que representa la gramática es vacío o no.

1. Aplicar el Algoritmo A para eliminar los símbolos no terminales que no generan cadenas de
terminales
2. Aplicar el Algoritmo B para eliminar de la gramática obtenida en el paso 1. los símbolos inaccesibles
Algoritmo A: Eliminación de los no terminales inútiles
Entrada: GLC G = ( N, , P, S )
Salida: Determina si L(G) =  , construye G' = (NU, , P', S), tal que L(G) = L(G') y donde NU  N, y es el
conjunto de no terminales que generan cadenas de terminales.

1. N0 = , i = 1

2. Sea Ni = { A | A    P,   { Ni-1   }* }  Ni-1

3. Si Ni  Ni-1, entonces i = i +1 e ir al paso 2., sino NU = Ni

4. Si S NU entonces L(G)  , sino L(G) = 

5. Si L(G)   entonces construir G' = (NU, , P', S). Aquí P' es el conjunto de reglas de producción de P
que solo involucran símbolos de NU  .

Este algoritmo construye iterativamente el conjunto NU que contendrá todos los símbolos no terminales capaces
de generar cadenas de terminales. Como consecuencia de este resultado se conocerá, al aplicar el algoritmo, si
el lenguaje generado por la gramática es distinto del vacío, a partir de la pertenencia o no del axioma al
conjunto NU.

Algoritmo B: Eliminación de símbolos inaccesibles


Entrada: GLC G = ( N, , P, S )
Salida: G' = ( N', ', P', S ), tal que:
(a) L(G) = L(G') y
(b)  X  N'  ',  ,   { N'  ' }* tales que S *G´ X

1. Sea N0 = { S }, i = 1

2. Sea Ni = { X | A  X  P, y X  N  , A  Ni-1 }  Ni-1

3. Si Ni  Ni-1 entonces i = i + 1 e ir al paso 2. sino NA = Ni

4. Construir G' = ( N', ', P, S) de la siguiente forma:


N' = NA  N
' = NA  
P' es el conjunto de reglas de producción de P que solo involucran símbolos de N´  ´

Este algoritmo construye iterativamente el conjunto NA que contendrá todos los símbolos, terminales o no, que
podrán aparecer en alguna forma sentencial. Inicialmente este conjunto solo esta formado por el axioma, el cual
es siempre accesible. En el siguiente paso se añaden los símbolos que son accesibles a través del axioma y
luego los accesibles a partir de los recién incluidos, y así sucesivamente, hasta que no es posible agregar mas
símbolos al conjunto.

Ejemplo:
Sea G = ( { S, A, B }, { a, b }, P, S )

Donde P: Sa|A
A  AB
Bb

Algoritmo A
1. N0 = , i = 1
2. N1 = { S, B }
2. N2 = { S, B }
3. NU = { S, B }
4. S  NU, luego L(G)  
5. G' = ({ S, B }, { a, b }, { S  a, B  b }, S)

Algoritmo B
1. N0 = { S }
2. N1 = { S, a }
2. N2 = { S, a }
3. NA = { S, a }
4. G'' = ( { S }, { a }, { S  a }, S )

A partir de este mismo ejemplo puede observarse que el orden de aplicación de los algoritmos A y B es
importante. Si se aplica primero el algoritmo B y luego el A puede obtenerse un resultado diferente, y como
consecuencia la gramática resultante puede aún contener algunos símbolos inútiles. La razón es que puede
darse el caso, como el del ejemplo, de un símbolo que sea accesible en la gramática a través de algún no
terminal inútil. Este es el caso en el ejemplo del símbolo terminal b. Al aplicar los algoritmos en orden inverso la
gramática resultante es:

G'' = ({ S, B }, { a, b }, { S  a, B  b }, S), la cual contiene dos símbolos inaccesibles, B y b.

3- Gramática libre de e-producciones

El siguiente algoritmo permite convertir una GLC a una GLC libre de e-producciones.

Una GLC G = ( N, , P, S ) es libre de e-producciones si cumple alguna de las siguientes condiciones:

a) P no contiene ninguna e-producción


b) P contiene una sola e-producción que es S e y además S no aparece en la parte derecha de
ninguna regla de producción

Las gramáticas libres de e-producciones son de gran utilidad pues otros tipos de gramáticas como las propias,
las que no son recursivas a la izquierda, etc. pueden ser de esas formas por el hecho de que una vez que un no
terminal aparece en una forma sentencial nunca podrá derivar a la cadena vacía, siempre como mínimo
derivara a un símbolo terminal (excepto quizás la derivación S  e, lo cual lo garantiza la definición de
gramática libre de e-producciones.

Algoritmo: Convertir una GLC en GLC libre de e-producciones


Entrada: GLC G = ( N, , P, S ).

Salida: G' = ( N', , P', S' ), tal que L(G) = L(G') y G' es libre de e-producciones

1. Construir Ne = { A | A  N y A +G e }

2. Determinar el nuevo conjunto de reglas de producción P' de la siguiente forma:

a) Si A  0112...kk para 1  i  k con i  Ne y no hay símbolos de Ne en ningún j,


entonces añadir a P' todas las reglas de producción de la forma:
A  0X11X2...Xkk donde Xi es i ó e
sin adicionar A  e a P'
b) Si S  Ne entonces adicionar a P' las reglas de producción:
S'  S |e, y hacer N' = N  { S' }, de lo contrario hacer N' = N y S' = S

3. Obtener G' = ( N', , P', S' )


El algoritmo construye inicialmente el conjunto Ne, el cual contiene todos los símbolos no terminales que pueden
derivar a la cadena vacía. Luego determina las reglas de producción que se obtienen a partir de todas las
combinaciones de reglas en las que aparecen o no los símbolos de N en las partes derechas.

Ejemplo:
Sea G = ( { S }, { a, b }, P, S )
P: S  aSbS | bSaS | e

1. Ne = { S }
2a. P' = S  aSbS | aSb | abS | ab | bSaS | bSa | baS | ba
2b. S  Ne luego se añade a P' S'  S | 
3. G' = ( { S, S' }, , P', S' )

4- Eliminación de producciones sencillas

Otro algoritmo útil es el eliminar las producciones sencillas en un GLC.

Una producción es sencilla en una GLC = ( N, , P, S ) si es de la forma A  B donde A y B  N

Algoritmo: Eliminar las producciones sencillas en una GLC


Entrada: GLC G = ( N, , P, S ) libre de e-producciones
Salida: G' = ( N, , P', S ), tal que L(G) = L(G') y G' no tiene producciones sencillas

1. Para cada símbolo no terminal A  N construir el conjunto NA tal que:


NA = { B | A * B }, a través de los siguientes pasos:
a) N0 = { A}, i = 1
b) Sea Ni = { B | C  B  P, C  Ni-1, B  N }  Ni-1
c) Si Ni  Ni-1 entonces i = i + 1 e ir al paso 1b) si no NA = Ni

2. Construir P' de la siguiente forma:


Para todo A  N hacer
Para todo B  NA
Si B    P y no es una producción sencilla, añadir a P' A  

3. Construir G' = ( N, , P', S )

Ejemplo:
Sea GE = ( { E, T, F }, { +, *, (, ), a }, P, E )
Donde P: E  E+T | T
T  T*F | F
F  (E) | a

1. NE = { E, T, F } NT = { T, F } y NF = { F }
2. P' : E  E+T | T*F | (E) | a
T  T*F | (E) | a
F  (E) | a
3. Construir G' = ( { E, T, F }, , P', E )

5- Gramática propia

Una GLC se dice que es propia si cumple las condiciones siguientes:


a) No posee símbolos inútiles
b) Es libre de e-producciones
c) Es libre de ciclos
Una GLC G = ( N, , P, S ) se dice libre de ciclos si no existe una derivación de la forma A A para algún A
 N.

Un algoritmo muy simple para eliminar los ciclos de una gramática puede ser:

1. Convertirla a gramática libre de e-producciones


2. Eliminarle las producciones sencillas

6- Eliminación de recursividad izquierda

A continuación analizaremos un algoritmo que permite eliminar la recursividad izquierda en una GLC. Este
algoritmo es de gran importancia para la implementación de la fase de análisis sintáctico de un compilador pues
existen métodos para la construcción de analizadores sintácticos a partir de la GLC que genera el lenguaje, que
imponen la restricción a la gramática de que no pueden ser recursivas a la izquierda. Analizaremos
primeramente algunos conceptos.

Definición: Un no terminal A en una GLC G = ( N, , P, S ) se dice que es recursivo si:

A A para algún  y   { N   } *


Si  = e entonces A es recursivo a la izquierda
Si  = e entonces A es recursivo a la derecha

Una gramática con al menos un no terminal recursivo a la izquierda (derecha) se dice que es recursiva a la
izquierda (derecha).

Un no terminal A posee recursividad izquierda inmediata si al menos una A-producción es de la forma


A  A, donde   { N   }+.

Veremos primeramente un algoritmo para eliminar la recursividad izquierda inmediata de un no terminal.

Algoritmo: Eliminación de la recursividad izquierda inmediata de un no terminal A


Entrada: GLC G = ( N, , P, S ) propia y A  N
Salida: G' = ( N  { A' }, , P', S ) tal que L(G) = L(G') y G no posee recursividad izquierda inmediata para el no
terminal A.

1. Sean
A  A1 | A2 | ... | An | 1 | 2 | ... | m
todas las A-producciones en P y ningún i comienza con A, entonces se construye G' = ( N  { A' }, ,
P', S ) donde P' es P con las A-producciones anteriores reemplazadas de la siguiente forma:
A  1 | 2 | ... | m | 1A' | 2A' | ... | mA'
A'  1| 2 | ... | n | 1A' | 2A' | ... | nA'
y A' es un nuevo símbolo no terminal

Otra variante del algoritmo que genera menos reglas de producción pero introduce e-producciones es la
siguiente:
A  1A' | 2A' | ... | nA'
A'  1A'| 2A' | ... | nA' | 

Ejemplo: Para la Gramática G E definida anteriormente la eliminación de la recursividad a la izquierda inmediata


resulta:
El no terminal F no posee recursividad a la izquierda, por lo que sus producciones se mantienen tal cual.
Es necesario eliminar la recursividad asociada a los no terminales E y T.
Las reglas E  E+T | T se transforman en las reglas:
E  T | TE’
E’  +T | +TE’
Las reglas T  T*F | F se transforman en las reglas:
T  F | FT’
T’  *F | *FT’
Luego, la gramática resultante es:
Sea G’E = ( { E, E’, T, T’, F }, { +, *, (, ), a }, P’, E )
Donde P‘: E  T | TE‘
E’  +T | +TE’
T  F | FT’
T’  *F | *FT’
F  (E) | a

Como puede apreciarse, este algoritmo transforma la recursividad a la izquierda en recursividad a la derecha,
pero la recursividad a la derecha por lo general no es un problema para los algoritmos de compilación.

El algoritmo anterior elimina la recursividad izquierda inmediata para un no terminal pero no elimina la
recursividad izquierda general en una GLC.

Algoritmo: Eliminación de la recursividad izquierda general


Entrada: GLC G = (N, , P, S) propia
Salida: G' = (N', , P', S) tal que L(G) = L(G') y G' no posee recursividad izquierda

1. Organizar los no terminales en algún orden A1, A2, ... An Ai  N

2. Determinar P' de la siguiente forma:


Para i = 1 hasta n hacer
a) Para j = 1 hasta i-1 hacer
Reemplazar cada producción de la forma A i  Aj por las producciones
Ai  1 | 2 | … donde Aj  1| 2 | … son las actuales Aj-producciones
b) Eliminar la recursividad izquierda inmediata de A i

7- Forma normal de Chomsky

Definición: Una GLC G = (N, , P, S) se dice que está en Forma Normal de Chomsky (FNC), si cada
producción en P tiene alguna de las formas siguientes:
a) A  BC con A, B, C  N ó
b) A  a con A  N, a   ó
c) Si   L(G), entonces S    P y S no aparece en la parte derecha de ninguna regla de producción.

Algoritmo

A partir de una GLC propia G = (N, , P, S) construimos una GLC equivalente expresada en FNC:
1) A P' pertenecen todas las reglas A  a  P
2) A P' pertenecen todas las reglas A  BC  P
3) Si S    P, entonces añadir S   a P'
4) Para cada regla de producción de la forma:
A  X1X2…Xk  P con k>2, añadir a P' el siguiente conjunto de reglas:
Sustituir Xi por Xi' si Xi   y Xi' es un nuevo no terminal
A  X1'<X2…Xk>
<X2…Xk>  X2'<X3…Xk>

donde cada <Xi…Xk> es un nuevo no terminal.
5) Para cada producción de la forma A  X1X2 donde X1 o X2 o ambos pertenecen a , añadir a P’ la
producción
A  X1'X2'
6) Para cada no temirnal a' introducido en los pasos 4 o 5, añadir a P' la producción
a'  a
Finalmente sea N' el conjunto formado por N junto con todos los nuevos no terminales creados. La nueva
gramática será G' = (N', , P', S).

Ejemplo: La regla S  aSbS expresada en FNC quedaría:


S  a’<SbS>
<SbS>  S<bS>
<bS>  b’S
a’ a
b’ b

8- Forma normal de Greibach

La Forma Normal de Greibach (FNG) tiene gran importancia para el análisis sintáctico, ya que en ella la
gramática tiene la propiedad de que cada una de sus reglas comienza con un terminal en la parte derecha.

Definición: Una GLC G = (N, , P, S) se dice que está en FNG, si es libre de -producciones y cada
producción es de la forma A  a, con a   y   N*.

Lema: Sea G = (N, , P, S) una gramática sin recursividad a la izquierda, entonces existe un orden lineal < en
N, tal que si A  B  P, entonces A<B.

Algoritmo (Conversión a la FNG):


Sea una GLC G = (N, , P, S) propia sin recursividad a la izquierda.
1) Construir un orden lineal en N tal que para toda A-producción se tenga que ella comienza por un terminal
o algún no terminal B tal que A<B, sea
N = {A1,A2,…,An} tal que A1 < A2 < … < An.
2) Sea i=n-1
3) Si i=0 ir al paso 5), de lo contrario reemplazar cada producción de la forma:
Ai  Aj donde j>i por
Ai  1 | 2 | ... | m, donde Aj  1 | 2 | ... | m son las Aj-producciones.
4) Sea i=i-1 e ir al paso 3.
5) En este punto todas las producciones (excepto S  , en caso de existir) comienzan con un terminal.
Para cada producción A  aX1…Xk, reemplazar aquellos Xj que sean terminales por un nuevo no terminal
Xj'.
6) Para todos los Xj' introducidos añadir la producción Xi'  Xi.

También podría gustarte