unidad 3.pdf

28
Autómatas Push-Down 1 . Introducción Los autómatas Push-Down son los autómatas más importantes entre las máquinas de estados finitos y las de Turing. Su operación está íntimamente relacionada con muchos procesos computacionales, en especial con el análisis y la traducción de lenguajes artificiales. Este tema trata fundamentalmente de aceptores Push-Down y su relación con gramáticas libres de contexto. Figura 1 : Aceptor "Push-Down" en la Configuración (q, ϕ , σ ) No se permite que la máquina mueva su cabezal de entrada hacia la izquierda, por lo tanto debe examinar los símbolos sobre su cinta de entrada en el estricto orden en que han sido escritos. La máquina comienza con la cinta de almacenamiento completamente en blanco (# en cada segmento), escribe un símbolo en esta cinta cada vez que mueve su cabezal de almacenamiento hacia la derecha, y lee un símbolo desde esta cinta de almacenamiento cada vez que mueve el cabezal de almacenamiento hacia la izquierda. Toda información que se encuentre a la derecha de este cabezal es irrecuperable, ya que será sobre-escrita cuando el cabezal se mueva hacia la derecha. Dado que el string escrito en la cinta de almacenamiento, que puede ser infinitamente largo, puede afectar el comportamiento del autómata, es necesario que el aceptor Push-Down tenga una memoria ilimitada. Sin embargo, la información más recientemente escrita debe ser la primera en ser recuperada. Este mecanismo de acceso limitado a lo que se encuentra almacenado, es muy común en la práctica computacional y se conoce como una pila "Push-Down" debido a que tiene como regla de recuperación la siguiente: "último en entrar, primero en salir" ("Last In, First Out"). 1 .1. Aceptores Push-Down. Definiciones Definición 1 : Un aceptor Push-Down (APD) es una tupla de seis elementos: M = (Q, S, U, P, Ι , F) en la cual: Q es el conjunto finito de estados de la unidad de control S es un alfabeto finito de entrada U es un alfabeto finito de la pila o "stack" P es el programa de M Ι Q es un conjunto de estados iniciales F Q es un conjunto de estados finales o estados de aceptación El programa P es una secuencia finita de instrucciones, cada una de las cuales tiene una de las siguientes formas: En cada caso, el estado q es la etiqueta o rótulo de la instrucción, y el estado q' es el estado sucesor . Cada estado en Q rotula como máximo un tipo de instrucción read, write o scan . Si q es un estado cualquiera, ϕ es un string cualquiera de S* y σ es cualquier string en U*, luego (q, ϕ , σ ) es una configuración de M. El string σ se llama usualmente pila o "stack " y el símbolo que se encuentra al tope de la misma se conoce como símbolo tope de la pila . Una configuración (q, ϕ , σ ) es una descripción completa del estado total de un aceptor Push-Down en algún punto en su análisis de una cinta de entrada. Esto se interpreta como se muestra en la Figura 1: La unidad de control se encuentra en el estado q; el prefijo ϕ del string de entrada ya ha sido explorado ("scanned") y el cabezal de entrada se posiciona en el último símbolo de ϕ ; la pila σ es el contenido de la cinta de almacenamiento, y el cabezal de la pila se posiciona en el último símbolo de σ . La definición siguiente ( Definición 2 ) va a especificar cómo la ejecución de una instrucción de un APD, lo hace evolucionar de un estado a otro, tal como lo ilustra la Figura 2. Las instrucciones scan leen símbolos sucesivos de la cinta de entrada, las instrucciones write almacenan símbolos en la pila, y las instrucciones read recuperan símbolos de la pila.

Upload: julioces01

Post on 09-Feb-2016

237 views

Category:

Documents


3 download

TRANSCRIPT

Page 1: Unidad 3.pdf

Autómatas Push-Down 1 . Introducción

Los autómatas Push-Down son los autómatas más importantes entre las máquinas de estados finitos y las de Turing.

Su operación está íntimamente relacionada con muchos procesos computacionales, en especial con el análisis y la traducción de lenguajes artificiales.

Este tema trata fundamentalmente de aceptores Push-Down y su relación con gramáticas libres de contexto.

Figura 1: Aceptor "Push-Down" en la Configuración (q, ϕ, σ)

No se permite que la máquina mueva su cabezal de entrada hacia la izquierda, por lo tanto debe examinar los

símbolos sobre su cinta de entrada en el estricto orden en que han sido escritos. La máquina comienza con la cinta de almacenamiento completamente en blanco (# en cada segmento), escribe un símbolo en esta cinta cada vez que mueve su cabezal de almacenamiento hacia la derecha, y lee un símbolo desde esta cinta de almacenamiento cada vez que mueve el cabezal de almacenamiento hacia la izquierda. Toda información que se encuentre a la derecha de este cabezal es irrecuperable, ya que será sobre-escrita cuando el cabezal se mueva hacia la derecha.

Dado que el string escrito en la cinta de almacenamiento, que puede ser infinitamente largo, puede afectar el comportamiento del autómata, es necesario que el aceptor Push-Down tenga una memoria ilimitada. Sin embargo, la información más recientemente escrita debe ser la primera en ser recuperada. Este mecanismo de acceso limitado a lo que se encuentra almacenado, es muy común en la práctica computacional y se conoce como una pila "Push-Down" debido a que tiene como regla de recuperación la siguiente: "último en entrar, primero en salir" ("Last In, First Out").

1 .1. Aceptores Push-Down. Definiciones Definición 1: Un aceptor Push-Down (APD) es una tupla de seis elementos:

M = (Q, S, U, P, Ι, F) en la cual:

Q es el conjunto finito de estados de la unidad de control S es un alfabeto finito de entrada U es un alfabeto finito de la pila o "stack" P es el programa de M Ι ⊆ Q es un conjunto de estados iniciales F ⊆ Q es un conjunto de estados finales o estados de aceptación

El programa P es una secuencia finita de instrucciones, cada una de las cuales tiene una de las siguientes

formas:

En cada caso, el estado q es la etiqueta o rótulo de la instrucción, y el estado q' es el estado sucesor. Cada

estado en Q rotula como máximo un tipo de instrucción read, write o scan. Si q es un estado cualquiera, ϕ es un string cualquiera de S* y σ es cualquier string en U*, luego (q, ϕ, σ) es

una configuración de M. El string σ se llama usualmente pila o "stack" y el símbolo que se encuentra al tope de la misma se conoce como símbolo tope de la pila.

Una configuración (q, ϕ, σ) es una descripción completa del estado total de un aceptor Push-Down en algún punto en su análisis de una cinta de entrada. Esto se interpreta como se muestra en la Figura 1: La unidad de control se encuentra en el estado q; el prefijo ϕ del string de entrada ya ha sido explorado ("scanned") y el cabezal de entrada se posiciona en el último símbolo de ϕ; la pila σ es el contenido de la cinta de almacenamiento, y el cabezal de la pila se posiciona en el último símbolo de σ.

La definición siguiente (Definición 2) va a especificar cómo la ejecución de una instrucción de un APD, lo hace evolucionar de un estado a otro, tal como lo ilustra la Figura 2. Las instrucciones scan leen símbolos sucesivos de la cinta de entrada, las instrucciones write almacenan símbolos en la pila, y las instrucciones read recuperan símbolos de la pila.

Page 2: Unidad 3.pdf

a) Movimiento "Scan"

b) Movimiento "Write"

c) Movimiento "Read"

Figura 2: Movimientos de un aceptor "Push-Down" Definición 2: Sea M un aceptor Push-Down con un programa P, y supongamos que el string ω ∈ S* está escrito sobre la cinta de entrada. Las instrucciones del programa de la máquina M son aplicables de acuerdo a las siguientes reglas: 1.- Una instrucción q] scan (s, q') es aplicable en una configuración (q, ϕ, σ) si ϕs es un prefijo de ω. Al ejecutar

esta instrucción, M mueve su cabezal de entrada un segmento a la derecha, observa el símbolo s inscripto en él, y entra al estado q'. Representamos un movimiento de scan con la siguiente notación:

S (q, ϕ, σ) → (q', ϕs, σ)

2.- Una instrucción q] write (u, q') es aplicable en cualquier configuración (q, ϕ, σ). Al ejecutar esta instrucción, M mueve el cabezal en la pila un segmento a la derecha, imprime allí el símbolo u, y entra en el estado q'. Representamos un movimiento write con la siguiente notación:

W (q, ϕ, σ) → (q', ϕ, σu)

3.- Una instrucción q] read (u, q') es aplicable en una configuración cualquiera (q, ϕ, σ) en la cual σ = σ'u. Al ejecutar esta instrucción, M "observa" el símbolo u bajo el cabezal de la pila, mueve el cabezal sobre la pila un segmento a la izquierda, y entra al estado q'. Representamos un movimiento de read con la siguiente notación:

R (q, ϕ, σ'u) → (q', ϕ, σ')

Si M tiene la siguiente secuencia de movimientos: (q0, ϕ0, σ0) → (q1, ϕ1, σ1) → .... → (qk, ϕk, σk)

(donde cada movimiento es un scan, read o write), la misma puede sintetizarse de la siguiente forma: (q0, ϕ0, σ0) ⇒ (qk, ϕk, σk)

Un aceptor Push-Down comienza su operación con el control en un estado inicial y los cabezales posicionados

en los numerales iniciales de sus respectivas cintas, como se muestra en la figura siguiente:

Figura 3.a: Configuración Inicial de un aceptor

La máquina pasa a través de una secuencia de operaciones, cada una resultante de la ejecución de una

instrucción aplicable en la configuración precedente. La operación continúa hasta que se alcanza una configuración en la cual no hay instrucciones aplicables. Si en un dado punto todos los símbolos de algún string de entrada han sido inspeccionados ("scanned"), la pila está vacía y la unidad de control está en un estado final, luego el string ϕ es aceptado por M. La configuración de aceptación se observa en la siguiente figura:

Page 3: Unidad 3.pdf

Figura 3.b: Configuración de Aceptación de un aceptor

Definición 3: Una configuración inicial de un aceptor Push-Down es cualquier configuración (q, λ, λ) en la cual q es un estado inicial de M. Una configuración final de M es cualquier configuración (q', ϕ, λ), donde q' es un estado final de M y ϕ es un prefijo del string escrito sobre la cinta de entrada de M. El string ϕ es aceptado por M sólo si M tiene una secuencia de movimientos:

(q, λ, λ) ⇒ (q', ϕ, λ) q ∈ Ι, q' ∈ F El lenguaje reconocido por M es el conjunto de strings aceptados por M.

1 .2. Ejemplos: Lenguajes de tipo Imagen Especular

La Figura 4 muestra el programa de un aceptor Push-Down Mcm con alfabeto de entrada S = { a, b, c } y de pila U = { a, b }. El estado 1 será por convención el estado inicial. Los nodos del diagrama de estados representan los estados de la unidad de control, y cada nodo tiene en su interior las letras S, R o W de acuerdo al tipo de instrucción que el estado está rotulando. Los estados iniciales y de aceptación son identificados de la misma manera que en el caso de los autómatas de estados finitos.

Figura 4: Programa y diagrama de estados de un aceptor Push-Down

Un estudio del programa o del diagrama de estados del autómata revela que a lo sumo una sola instrucción es

aplicable a una configuración cualquiera de Mcm; por lo tanto, el comportamiento de la máquina es determinado inequívocamente por el string de entrada ω. La máquina comienza su operación en el estado 1 y pasa a través de dos etapas: 1.- En la etapa de almacenamiento, copia en la pila la porción de ω previa a encontrar el símbolo c. 2.- En la etapa de comparación trata de encontrar igualdades entre los símbolos almacenados en la pila y los

restantes símbolos del string de entrada.

Si la porción de ω que sigue a la letra c es exactamente el reverso del string almacenado en la pila, M vaciará la cinta de almacenamiento inmediatamente después de explorar la última letra de ω, dejando a Mcm en la

configuración de aceptación (4, ω, λ). Luego, Mcm acepta cada string que tenga la forma ω = ϕ c ϕR, donde ϕ ∈ (a ∪ b)*. Por ejemplo, la secuencia de movimientos para la cual Mcm acepta el string ω = abcba es:

Si la letra explorada por Mcm en el estado 4 no coincide con la última letra de la pila, o si quedan símbolos en la pila luego de que ω ha sido explorado, Mcm se detendrá con una pila no vacía y se rechazará la entrada. El rechazo de ω = abca se ilustra con la siguiente secuencia de movimientos:

Concluímos que el lenguaje reconocido por Mcm es:

Lcm = { ϕ c ϕR / ϕ ∈ (a ∪ b)* }

Este lenguaje se conoce como lenguaje imagen especular con marcador central. Es generado por la siguiente gramática libre de contexto:

Gcm: ∑ → S S → bSb S → aSa S → c

Page 4: Unidad 3.pdf

El aceptor Push-Down Mcm es determinístico, ya que para una configuración cualquiera nunca hay opción a

más de un movimiento. Una leve modificación del lenguaje Lcm requerirá un Aceptor Push-Down No Determinístico. Por ejemplo, consideremos el lenguaje:

Lmi = { ϕ ϕR / ϕ ∈ (a ∪ b)* - λ } que es simplemente conocido como lenguaje imagen especular.

A diferencia del lenguaje Lcm, no hay un símbolo especial en los strings de Lmi que indique cuándo el aceptor Push-Down debe cambiar del modo "Pila de almacenamiento" al modo "Comparación". La pregunta es entones: Existe un aceptor Push-Down para Lmi? Y la respuesta es sí. En realidad es fácil modificar Mcm para obtener un autómata que tenga una secuencia de aceptación para cada string de Lmi. La modificación es la siguiente: En lugar de aguardar el símbolo "c", permitimos que la máquina Mmi cambie al modo comparación cada vez que escribe en la pila un símbolo que ha explorado. La máquina resultante es:

Figura 5: Aceptor Push-Down No Determinístico

Este comportamiento debe ser permitido debido a que no hay forma de que la máquina determine cuándo ha

examinado la primera mitad del string "imagen especular". Es decir, le permitiremos a la máquina "probar" luego de cada símbolo explorado si debe cambiar o no al modo comparación.

Las posibles secuencias de movimientos de Mmi para el string de entrada aabbaa se muestran en la siguiente figura, donde se ve que se alcanzan configuraciones de aceptación para los strings aa y aabbaa por medio de secuencias de movimientos únicas; todas las otras secuencias terminan en configuraciones de rechazo.

Figura 6: Comportamiento No Determinístico de un aceptor Push-Down 2 . Aceptores Propios y Libres de Ciclos

Con el objeto de hacer más simple el tratamiento de ciertos temas relacionados con los Aceptores Push-Down, es conveniente restringir nuestra atención a la subclase de aceptores cuya habilidad para reconocer lenguajes es equivalente a la de todos los aceptores Push-Down. Comenzaremos con la clase denominada "Aceptores Push-Down Propios". Con la idea de demostrar que no hay ninguna pérdida de generalidad, desarrollaremos una construcción por medio de la cual cualquier APD puede ser transformado en un Aceptor Push-Down Propio (APDP).

2 .1. Aceptores Push-Down Propios

Nuestra definición de APD no permite ciertos comportamientos anómalos o no productivos. En particular: 1.- Un APD no puede exlorar más allá del final del string que está en la cinta de entrada, debido a que no puede

hacer un movimiento en el cual un "numeral" sea explorado. 2.- Un APD no puede mover su cabezal sobre la pila a la izquierda del primer segmento de la cinta de

almacenamiento, ya que no puede realizar un movimiento en el cual un "numeral" sea leído de la pila.

Sin embargo, otros dos tipos de comportamientos improductivos que pueden ocurrir, y que pueden conducir a dificultades en el análisis de las propiedades de aceptores Push-Down, son: 3.- Uno se produce cuando un movimiento de read ocurre inmediatamente después de un movimiento de write . 4.- El otro es una repetición sin fin de sucesivos movimientos de write (ciclo).

Supongamos que un APD M tiene una secuencia de movimientos en la cual un movimiento de write es seguido por un movimiento de read, y en esta secuencia no intervienen movimientos de scan:

Page 5: Unidad 3.pdf

Este par de movimientos no tiene un efecto neto además de hacer que la unidad de control cambie entre los

estados q y q". Esta secuencia de movimientos inconsistentes puede ocurrir solamente si M tiene una instrucción de escritura:

q] write (u, q') para la cual el estado sucesor q' es el rótulo de una instrucción read. Definición 4: En el programa de un APD M, una instrucción write :

q] write (u, q') es impropia si q' es el rótulo de una instrucción read cualquiera. Una APD es propio si su programa no contiene instrucciones impropias.

Las instrucciones impropias son fácilmente identificables en el diagrama de estados de un APD, y pueden ser eliminadas sin modificar el comportamiento de la máquina en lo referente al lenguaje reconocido. En particular, si M es un APD cualquiera, es posible construir un APDP mediante la adición y el borrado de instrucciones tal como sigue.

Transformación de un APD en un APDP

Etapa 1: Sean q y q' un par de estados cualesquiera para los cuales M tiene una secuencia de movimientos

que consiste de k movimientos de write , seguidos de k movimientos de read, para cualquier k≥1.

Cada vez que aparezca la instrucción p] move ( - , q)

adicionaremos la instrucción p] move ( - , q') , donde - significa cualquier símbolo.

Si el estado q es un estado inicial en M, hacemos q' un estado inicial en M'. La etapa 1 debe ser realizada para cada par de estados q y q' para los cuales M tiene una secuencia de

movimientos como la especificada anteriormente.

Etapa 2: Se borran las instrucciones impropias. Las instrucciones que quedan forman el programa de M'. Proposición 1: Sea M un APD cualquiera. Se puede construir un APDP M', tal que verifica que L(M')=L(M). No se demostrará. Ejemplo 1: En la figura siguiente se muestra un aceptor Push-Down que escribe un string perteneciente al conjunto (b*ba)* en su pila, y luego emplea a ésta para controlar la exploración de un string de entrada definido sobre (x ∪ y)*.

Conversión de un aceptor Push-Down a una forma propia La instrucción

2] write (a , 5) es impropia ya que el estado 5 rotula la siguiente instrucción de read:

Page 6: Unidad 3.pdf

5] read (a , 4)

Este aceptor tiene las siguientes secuencias de movimientos "sin resultado neto":

Por lo tanto, para obtener un APDP M', cada instrucción de M que tenga a los estados 1 o 2 como estados

sucesores, se duplica reemplazando al estado 1 (como sucesor) por los estados 3 y 5, y el estado 2 por 4. Siguiendo con la aplicación de la primer etapa del procedimiento de generación de M', como el estado 1 es

inicial, los estados 3 y 5 serán estados iniciales en M'. La figura (b) muestra con líneas de trazos las nuevas instrucciones y los nuevos estados iniciales.

La próxima etapa es la eliminación de las instrucciones impropias (aquellas en líneas de trazos en la figura (c)).

Estas son borradas para obtener el aceptor M' mostrado en la figura (d). Mediante la Proposición 1 sabemos que L(M')=L(M). Por ejemplo, la secuencia de movimientos

por la cual M acepta el símbolo y, se transforma simplemente en:

en la máquina M'.

Es importante aquí detenerse a analizar cómo un APDP acepta el string vacío. En un APD cualquiera, una

secuencia que acepta a λ no deberá contener movimientos de scan. Mas aún, cualquiera de estas secuencias deberá contener un número equivalente de movimientos de read y write y además, al menos un movimiento de write debe preceder cualquier movimiento de read.

En un APDP un movimiento de write no puede estar inmendiatamente seguido de un movimiento de read; esto lleva a concluir que λ debe ser aceptado sin ningún tipo de movimiento, lo que equivale a decir que un APDP acepta a λ sólo si su estado inicial es de aceptación.

2 .2. Aceptores Push-Down Libres de Ciclos

La segunda forma de comportamiento improductivo se presenta si un APD contiene un ciclo de instrucciones write . Definición 5: Un APDP está libre de ciclos si su programa no contiene ciclos de instrucciones write :

q1] write (u1, q2) q2] write (u2, q3)

........ qn] write (un, q1)

Supongamos que un APDP M tiene un ciclo de instrucciones en su programa. Si M ejecutase una instrucción

del ciclo, luego la máquina imprimirá una repetición sin fin de los símbolos u1, u2, ...., un. Dado que los estados q1, q2, ...., qn del ciclo no pueden aparecer en ninguna configuración de una secuencia de movimientos de aceptación, el ciclo puede ser descartado del programa de M sin alterar el lenguaje definido por M. Proposición 2: Para un APDP M, se puede construir un APD libre de ciclos M', tal que L(M')=L(M).

La Proposición 2 es válida para aceptores no determinísticos, a pesar de que una demostración directa de este hecho es muy difícil. Sin embargo, se dará una prueba indirecta a partir de la correspondencia entre aceptores y lenguajes libres de contexto que se desarrollará en las próximas secciones. Demostraremos que es posible construir un aceptor libre de ciclos para cualquier lenguaje libre de contexto. Luego demostraremos que es posible encontrar una gramática libre de contexto para el lenguaje definido por un APD cualquiera, libre de ciclos o no. 3 . Aceptores Push-Down para Lenguajes Libres de Contexto

3 .1. Análisis Sintáctico

Los traductores de lenguajes artificiales (por ej., compiladores de programas) emplean varias etapas en su procesamiento. Cuando las sentencias válidas del lenguaje son especificadas por medio de una gramática con estructura de frases, la primer etapa del proceso de traducción construye un árbol de derivación para una dada sentencia. Cuando la sentencia no es ambigua, el único árbol de derivación asigna un tipo sintáctico a cada una de sus frases; luego el "anidamiento" de frases tipo en la derivación es empleado por el traductor para asignar

Page 7: Unidad 3.pdf

significado a la oración. La construcción de un árbol de derivación de acuerdo a una gramática específica se conoce como Análisis Sintáctico.

Consideremos la gramática GE, la cual es una porción de la gramática para la sentencia de asignación del

ALGOL ya vista al comenzar el curso.

GE: E → A A → T + A E → if B then A else E T → x B → A = A T → y A → T T → z

Consideremos ahora la forma metódica de construir un árbol de derivación para la oración

if x=y then z else x+y que es una frase de tipo sintáctico E.

Nuestro procedimiento seguirá cada posible cadena de pasos de derivación por la izquierda que la gramática permite. Este procedimiento se ilustra en la siguiente figura, donde las letras i, t y e son las abreviaturas para los símbolos terminales if, then y else.

Análisis Sintáctico "Top-Down"

Cada cadena de pasos de derivación comienza con la forma sentencial E. Primeramente, exploramos la

gramática buscando una producción con E como su parte izquierda y encontramos las siguientes reglas que son candidatas:

E → A E → i B t A e E Determinemos las consecuencias de aplicar la producción E → A. Debemos recordar que cuando el símbolo *

aparece, indica que hay producciones alternativas que pueden ser aplicadas en un punto dado. Por ejemplo, si aplicamos la primera de las producciones anteriores, obtenemos la línea (2), y de nuevo surgen dos producciones que son posibles de utilizar:

A → T A → T + A Probando A → T [se obtiene la línea (3)], encontramos que T puede ser reemplazada por las letras terminales

x, y o z [línea (4)], ninguna de las cuales coincide con la primer letra de la sentencia dada. Retrocedemos (haciendo "backtracking") a la línea (2) y probamos la alternativa mostrada en la línea (5). Una

vez más, todas las posibilidades fallan en la línea (6). Debemos retroceder a la línea (1), ya que hemos agotado todas las posibles reglas aplicables a A. Dado que era posible aplicar otra regla a E, la aplicamos y generamos la línea (7). Dado que el primer símbolo de la línea (7) coincide con el primer símbolo de la sentencia bajo estudio, este símbolo es subrayado.

Ahora el símbolo no terminal de la línea (7) es B, y hay una sola regla para B; luego la línea (8) se obtiene

inmediatamente. En la línea (8) probamos la primer regla aplicable a A para obtener la línea (9), y nuevamente marcamos este punto con * para indicar que una regla alternativa podría ser aplicada. Continuamos con esta metodología hasta la línea (17), marcando cada línea donde existe una alternativa con *, y subrayando la porción de la oración que coincide con la sentencia dada. En la línea (17) descubrimos que

i x=y t z e x es una frase de tipo E. Sin embargo, los dos últimos símbolos de la oración dada no coinciden con la hallada. Debemos retroceder a la línea (15) en donde la decisión más reciente fue tomada, y aplicamos la otra regla asociada con A. Esto da como resultado la línea (18) que nos conduce a la línea (21) que finalmente es exitosa.

Page 8: Unidad 3.pdf

La cadena de pasos de derivación exitosos especifican el siguiente árbol de derivación:

Este método de análisis sintáctico es llamado Procedimiento "Top-Down" debido a que el árbol de derivación

para una sentencia es originado a partir del nodo raíz (ubicado en el tope del árbol), evolucionando hacia las hojas, o sea hacia abajo. Existen también procedimentos "Bottom-Up" que construyen el árbol de derivación partiendo de los nodos hoja y evolucionando hacia la raíz.

3 .2. Construcción de Analizadores Sintácticos Push-Down

Es sencillo construir un APD que lleve a cabo la operación de análisis sintáctico "Top-Down". Sea G = (N, T, P, ∑) una gramática arbitraria libre de contexto. Mostraremos como construir un APD M que "derive" cualquier string definido en L(G).

Supongamos que

∑ ⇒ ϕ1A1β1 ⇒ ..... ⇒ ϕkAkβk ⇒ ω es la derivación por izquierda de ω para

Las formas sentenciales pertenecientes a esta derivación están representadas por configuraciones de M como

las que se muestran en la siguiente figura:

Representación de una forma sentencial ϕiAiβi en un aceptor Push-Down

Específicamente, una forma sentencial ϕiAiβi está representada por una configuración

(qR, ϕi, βiRAi) en la cual el string de símbolos de entrada que ha sido explorado es ϕi , el contenido de la pila o "stack" es el reverso de Aiβi y qR es un estado especial de M.

Cuando a M se le presenta una cinta de entrada conteniendo un string ω definido en L(G), el comportamiento de M será el de asumir una sucesión de configuraciones correspondientes a las formas sentenciales de cualquier derivación por izquierda de ω.

(q1, λ, λ) ⇒ (qR, λ, ∑) ⇒ (qR, ϕ1, β1RA1) ⇒ ..... ⇒ (qR, ϕk, βkRAk) ⇒ (qR, ω, λ) Debemos suministrar al aceptor un programa de instrucciones que lleve a cabo cada movimiento de la

secuencia: (qR, ϕi, βiRAi) ⇒ (qR, ϕi+1, βi+1RAi+1)

correspondiente a un paso de derivación en la gramática G. Cada vez que haya producciones alternativas aplicables a una forma sentencial, el aceptor también deberá tener un conjunto de movimientos alternativos. Por lo tanto, M deberá ser por lo general un autómata no determinístico.

El programa de M se realiza con dos operaciones básicas: "expansión" y "matching". La expansión

corresponde a la aplicación de una producción Ai → ψ a la forma sentencial ϕiAiβi . El "matching" determina cuándo los símbolos terminales ubicados al principio de ψ pueden coincidir con los próximos símbolos de la cinta de entrada. El símbolo tope de la pila determina cuál de estas operaciones ocurre: una letra no terminal indica expansión, mientras que una letra terminal indica "matching". El estado qR se usa para leer el símbolo tope de la pila.

1.- Expansión: Reemplaza la letra no terminal A ubicada al tope de la pila por ψR, el reverso del lado derecho de

alguna producción A → ψ definida en G. Retorna al estado qR. Esto se muestra en la siguiente figura:

Page 9: Unidad 3.pdf

La expansión de acuerdo a la regla A → ψ se implementa con las instrucciones:

qR] read (A, qA)

qA] write (ψR, qR) Aquí, se ha empleado una instrucción write generalizada, que es una abreviación obvia para una secuencia de

movimientos de write que apendizan el string ψR a la pila.

2.- Matching: Si el símbolo tope de la pila es una letra terminal t, se deberá explorar la próxima letra s ubicada sobre la cinta de entrada. Si s=t el "matching" (coincidencia) es exitoso y puede continuar. De otra forma, el matching falla y la operación finaliza. Esta operación se ilustra en la siguiente figura:

y se implementa por medio de las siguientes instrucciones:

qR] read (t, q t) q t] scan (t, qR) ambas para cada t ∈ T.

El reconocedor de L(G) se completa mediante la adición de la instrucción inicial:

qΙ] write (∑, qR) la cual inicializa la pila con la forma sentencial ∑. Los estados inicial y final de M son:

Ι = { qΙ } y F = { qR } Debemos incluir qΙ en el conjunto F si ∑ → λ es una producción de G. Estas reglas de construcción se resumen en la Tabla 1 y la forma general de M se muestra en la figura

ubicada a continuación. Tabla 1: Construcción de un Analizador Push-Down Dada: Una gramática libre de contexto G = (N, T, P, ∑) Construir: Un aceptor Push-Down M = (Q, T, U, P, Ι, F) tal que L(G) = L(M)

Sean: U = N ∪ T ∪ { ∑ } Q = { qΙ , qR } ∪ { qx / x ∈ U } Ι = { qΙ } qR ∈ F

Las instrucciones de M son: Para inicializar: qΙ] write (∑, qR) Para expandir: Por cada producción A → ψ en G donde A ∈ N ∪ { ∑ } y qR] read (A, qA)

ψ ∈ (N ∪ T)* - λ qA] write (ψR, qR) Para hacer "matching": Por cada símbolo qR] read (t, q t) t ∈ T q t] scan (t, qR) Para aceptar λ: Si G tiene ∑ → λ qΙ ∈ F

A modo de ejemplo aplicamos el procedimiento de esta tabla a la gramática GE usada para ejemplificar análisis

sintáctico.

Page 10: Unidad 3.pdf

Tabla 2: Construcción de un Aceptor Push-Down para L(GE) Dada GE: E → A B → A = A

E → i B t A e E T → x A → T T → y A → T + A T → z

Las instrucciones de ME son:

Para inicializar: qΙ] write (E, qR) Para expandir: qR] read (E, qE) (A, qA) (B, qB) (T, qT)

qE] write (A, qR) (EeAtBi, qR) qA] write (T, qR) (A+T, qR) qB] write (A=A, qR) qT] write (x, qR) (y, qR) (z, qR)

Para hacer "matching": qR] read (i, qi ) (t, qt ) (e, qe ) qR] read (+, q+) (=, q=) qR] read (x, qx) (y, qy) (z, qz) qi ] scan (i , qR) qt ] scan (t , qR) qe] scan (e, qR) q+] scan (+, qR) q=] scan (=, qR) qx] scan (x, qR) qy] scan (y, qR) qz] scan (z, qR)

4 . Gramáticas Libres de Contexto a Partir de un Aceptor Push-Down

4 .1. "Traverse Sets"

Un "traverse" que observa el string ω

Durante ese intervalo, la forma de la secuencia de movimientos realizada por M es:

(q, ϕ, σ) ⇒ (q', ϕω, σ) donde σ es el contenido inicial de la pila y el estado q rotula una instrucción write que agrega el símbolo no terminal A a la pila. Esta secuencia de movimientos, en la cual el cabezal sobre la pila retorna a una posición inicial sin haberse movido hacia la izquierda de dicha posición, es llamada un "traverse ", y el string ω sobre el cual ha avanzado el cabezal sobre la cinta de entrada se denomina string observado por el traverse .

De acuerdo al Teorema 1, la secuencia de movimientos indicada arriba se corresponde únicamente con la derivación:

ϕ A σR ⇒ ϕ ω σR en la gramática G. Esto es, el string terminal ω es derivable a partir del símbolo no terminal A.

Esto sugiere que, en general, el string observado por un "traverse" de un APD es derivable a partir de un

símbolo no terminal en una dada gramática. La construcción de una gramática a partir de un APD está basada en esta idea. De la misma forma que la noción de "conjunto final" ayudó en la construcción de gramáticas lineales por derecha a partir de aceptores de estados finitos (AEF), trataremos de utilizar la noción de "traverse set" para la construcción de gramáticas libres de contexto (GLC) a partir de un APD cualquiera, mediante la asociación de símbolos no terminales con los conjuntos "traverse". Definición 6: Sea M = (Q, S, U, P, Ι, F) un APDP. Una secuencia de movimientos de M

(q, ϕ, σ) ⇒ (q', ϕ', σ') es llamada un traverse de ω desde el estado q al estado q' si se cumple que: 1.- σ = σ' 2.- ϕω = ϕ' 3.- Cada configuración (qi, ϕi, σi) que ocurre en la secuencia de movimientos satisface que |σi | ≥ |σ|.

Supongamos que M es un APD construído a partir de una gramática libre de contexto G de acuerdo a la Tabla 1.

La figura siguiente ilustra el comportamiento de M desde el momento en que la misma escribe un símbolo no terminal A en la posición k de la pila hasta el momento en el que lee desde esa posición por primera vez.

Page 11: Unidad 3.pdf

En tal caso decimos que ω es el string observado por el "traverse". Para indicar que una secuencia de

movimientos es un "traverse" escribimos T

(q, ϕ, σ) ⇒ (q', ϕω, σ)

El conjunto Traverse T(q,q') es el conjunto de todos los strings observados por movimientos traverse que van de q a q'.

T T(q,q') = { ω ∈ S* / (q, ϕ, σ) ⇒ (q', ϕω, σ) para algún ϕ ∈ S* y algún σ ∈ U* }

Un traverse que no involucra ningún movimiento

T (q, ϕ, σ) ⇒ (q', ϕ, σ)

observa el string vacío y es llamado Traverse Trivial. Así, λ ∈ T(q,q) para cada q ∈ Q.

Esta definición se ha ilustrado en la figura anterior. A medida que el cabezal sobre la cinta de entrada explora los símbolos de ω, el cabezal sobre la pila realiza excursiones arbitrarias sobre la cinta de almacenamiento, pero jamás se mueve a la izquierda de la posición en la cual comienza y termina el traverse. Ejemplo 2: Consideremos el lenguaje de doble paréntesis Ldp, compuesto por todos aquellos strings con paréntesis balanceados, que pueden contener dos tipos de paréntesis. El lenguaje tiene la siguiente definición: 1.- ( ) y [ ] están definidos en Ldp. 2.- Si ω está definido en Ldp, también lo están (ω) y [ω]. 3.- Si ω y ϕ están definidos en Ldp, lo mismo ocurre con ωϕ.

Ldp contiene, por ejemplo, el string [( )]( ). Un aceptor Push-Down para este lenguaje se muestra en la siguiente figura:

APD para el lenguaje Ldp

El "traverse" por el cual este aceptor acepta [( )]( ) es:

Por lo tanto escribimos:

Este "traverse" contiene subsecuencias de movimientos que a su vez son "traverse":

Por lo tanto T(1,1) contiene los strings [( )]( ), [( )] y ( ), al igual que λ. También tenemos:

por lo que [ está en T(1,3) y finalmente tenemos:

por lo que ) está en T(1,4).

4 .2. Propiedades de los Conjuntos Traverse

De la figura que acompaña la definición de conjunto "traverse" surge claramente que las elecciones del string ϕ inicialmente explorado y de la pila inicial σ son arbitrarias en un "traverse". Proposición 3: Si

T (q, ϕ, σ) ⇒ (q', ϕω, σ)

es un "traverse" de ω para algún ϕ ∈ S* y algún σ ∈ U*, luego éste es un traverse de ω para cualquier ϕ en S* y cualquier σ en U*.

Si, para algún traverse de ω, los strings ϕ y σ son vacíos, luego tenemos que:

Page 12: Unidad 3.pdf

T

(q, λ, λ) ⇒ (q', ω, λ) es una secuencia de movimientos de aceptación para ω, si q ∈ Ι y q' ∈ F. Además, cualquier secuencia de aceptación de ω debe ser un "traverse" de ω, ya que el cabezal sobre la cinta de almacenamiento no puede efectuar ningún movimiento hacia la izquierda cuando la pila está vacía. Proposición 4: Un APD M acepta un string ω sí y sólo sí éste observa el string ω en un "traverse" desde algún estado inicial a algún estado final.

ω ∈ L(M) sí y sólo sí ω ∈ T(q,q'), q ∈ Ι, q' ∈ F por lo tanto el lenguaje reconocido por M es:

Consideremos ahora un "traverse" efectuado por M para el string vacío λ. Cualquiera de estos "traverse" no debe contener ningún movimiento de scan, y por lo tanto consiste de una secuencia de writes y reads. Dado que el "traverse" debe dejar al cabezal que se encuentra sobre la pila en su posición inicial, los movimientos de lectura y escritura deben ser de igual número. Más aún, el primer movimiento de un traverse de este tipo debe ser de write , ya que un read movería el cabezal a la izquierda de su posición inicial. Consecuentemente, es imposible completar el "traverse" sin dejar de tener en algún lugar un movimiento de read que siga inmediatamente a uno de write . Ya que ésto no sería posible si M fuese un aceptor propio, tenemos: Proposición 5: Sea M un aceptor Push-Down propio. Los únicos "traverses" posibles del string λ realizados por M son los "traverses" triviales.

T (q, ϕ, σ) ⇒ (q, ϕ, σ) q ∈ Q, ϕ ∈ S*, σ ∈ U*

Es decir, λ ∈ T(q,q') sí y sólo sí q = q'

Luego, cada "traverse" no trivial llevado a cabo por un APDP debe contener como mínimo un movimiento de scan.

4 .3. Construcción de una Gramática

En la construcción de una gramática a partir de un APD, queremos asociar una letra no terminal N(q,q') con ciertos conjuntos "traverse" T(q,q') del autómata (en forma análoga al uso de conjuntos finales para el análisis de AEF). Cada letra no terminal denotará exactamente aquellos strings que son miembros de los conjuntos "traverse" correspondientes. Con el propósito de obtener las producciones gramaticales, debemos relacionar la pertenencia de un string a un conjunto "traverse" con la pertenencia de sus substrings a otros conjuntos "traverse". Será necesario introducir una terminología adicional para desarrollar estas ideas. Definición 7: En un "traverse"

realizado por un APD, sea σ el contenido inicial y final de la pila. Un movimiento de write en el "traverse" es llamado movimiento de write básico si es realizado desde una configuración en la cual la pila contiene σ y un movimiento de read es llamado movimiento de read básico si deja a la pila conteniendo σ. Un movimiento de write básico y un movimiento de read básico forman un par de igualdad o coincidencia si el movimiento de write precede el movimiento de read, y ningún otro write o read básico interviene.

En un par de igualdad o coincidencia de movimientos básicos, el movimiento de read lee el símbolo colocado sobre la pila por el movimiento de write . Ejemplo 3: En el Ejemplo 2, el "traverse":

consiste de la siguiente secuencia de movimientos:

En este "traverse" hay dos pares de igualdad, que están indicados por las líneas contínuas. La secuencia de

movimientos dentro del primer par de igualdad o coincidencia es el "traverse":

el cual contiene en sí mismo el par de igualdad indicado por la línea de trazos.

En este ejemplo, cada read básico coincide con un write básico en el mismo "traverse". Realmente, este debe ser siempre el caso. Para que en un "traverse" el cabezal sobre la pila retorne a su posición inicial, el aceptor debe leer todos los símbolos que han sido escritos en la pila desde el comienzo del "traverse". Si un símbolo ha sido escrito por un movimiento de write básico del "traverse", el movimiento que lee el símbolo debe ser el siguiente movimiento de read básico. Similarmente, cualquier símbolo leído por un read básico en un "traverse" debe haber sido escrito por el write básico precedente. Por lo tanto tenemos la siguiente proposición:

Page 13: Unidad 3.pdf

Proposición 6: Un "traverse" que contiene un read (write) básico también contiene el correspondiente write (read) básico.

Ahora estamos preparados para los resultados básicos que relacionan la pertenencia de un string a un conjunto "traverse" con la pertenencia de strings más simples a otros conjuntos "traverse". La Proposición 7 expresa formalmente el hecho que un conjunto "traverse" debe asumir una de cuatro formas mutuamente excluyentes. Proposición 7: Sea M un APDP y supongamos que:

es un "traverse" del string ω realizado por M; esto es, ω ∈ T(q,q'). Luego, exactamente una de las siguientes afirmaciones es cierta: 1.- El "traverse" consiste de un movimiento de scan simple.

2.- El "traverse" tiene más de un movimiento, y el movimiento final es un scan.

3.- El "traverse" tiene más de un movimiento, el movimiento final es un read, y el movimiento de write que

corresponde al mismo no es el primero.

4.- El "traverse" tiene más de un movimiento, el movimiento final es un read, y el write coincidente es el primer

movimiento del "traverse".

Las cuatro formas de un "traverse" que observa el string ω

La demostración de esta proposición no será efectuada. Sin embargo es claro que las cuatro afirmaciones son

mutuamente excluyentes y que todos los posibles casos están cubiertos.

Durante el estudio de los AEF encontramos que de las propiedades del diagrama de estados surgen naturalmente dos tipos de gramáticas. La construcción de gramáticas lineales por derecha se basa en una secuencia de movimientos que vincula algún estado especificado con algún estado final. Similarmente, la construcción de gramáticas lineales por izquierda se basa en una secuencia de movimientos que conectan un estado inicial cualquiera con un estado especificado. Cualquiera de estos métodos puede ser generalizado y aplicado a la construcción de gramáticas a partir de aceptores Push-Down.

El principio por el cual vamos a relacionar gramáticas con aceptores es el mismo ya empleado con anterioridad. Obtendremos las producciones de una gramática G a partir del programa de un APD M de forma tal que para cualquier string ω en S*

∗ ω ∈ T(q,q') en M sí y sólo sí N(q,q') ⇒ ω en G

donde T(q,q') es un conjunto "traverse" y N(q,q') es un símbolo no terminal correspondiente. Cada uno de los cuatro posibles tipos de "traverse" vistos en la proposición 7 resulta de ciertas instrucciones

específicas en el programa de la máquina M: Cada caso especifica relaciones entre conjuntos "traverse" que se

Page 14: Unidad 3.pdf

corresponden con relaciones entre conjuntos de strings derivables a partir de las letras no terminales de la gramática. Estas relaciones se muestran en la figura siguiente:

Relaciones entre tipos de "traverse" y etapas de derivación

Por lo tanto ahora nos preguntamos: Con qué pares de estados (q,q') debemos asociar las letras no terminales

N(q,q') de G? Dado que cada N(q,q') es asociado con un "traverse", necesitamos identificar el conjunto de estados B sobre los cuales los "traverse" pueden comenzar. Los conjuntos "traverse" relevantes a la construcción de una gramática son entonces:

{ T(q,q') / q ∈ B, q' ∈ Q } A partir de la proposición 4 sabemos que un "traverse" mediante el cual se acepta un string siempre comienza

en un estado inicial de M. Más aún, la figura anterior muestra que, cuando un "traverse" se descompone en "traverses" más simples de acuerdo a la proposición 7, cada "traverse" más simple comienza, ya sea desde el mismo estado inicial que el "traverse" que lo engloba, o desde el estado sucesor de un movimiento de write . De acuerdo a ésto, el conjunto B es:

B = Ι ∪ { q' / q] write (u,q') es una instrucción de M para algún q ∈ Q y algún u ∈ U }

El conjunto de símbolos no terminales de G es: N = { N(q,q') / q ∈ B, q' ∈ Q }

A partir de la proposición 7 y de la figura anterior podemos deducir las siguientes reglas de construcción de la

gramática G. Regla 1: Supongamos que M tiene la instrucción:

q] scan (s,q') q ∈ B Luego s ∈ T(q,q') y G tiene la producción:

N(q,q') → s Regla 2: Supongamos que M tiene la instrucción:

q"] scan (s,q') Luego si ψ es un string en T(q,q"), se sigue que ψs está en T(q,q'). Esto se expresa mediante la producción:

N(q,q') → N(q,q")s definida en G. Regla 3: Supongamos que M tiene las instrucciones:

q"] write (u,p) p'] read (u,q')

Si ψ y θ son strings no vacíos definidos en T(q,q") y T(p,p'), luego ω = ψθ es un string en T(q,q'). Esto es expresado mediante la producción:

N(q,q') → N(q,q")N(p,p') Regla 4: Supongamos que M tiene las instrucciones:

q] write (u,p) q ∈ B p'] read (u,q')

Luego cualquier string definido en T(p,p') también lo está en T(q,q'), como se expresa en la producción: N(q,q') → N(p,p')

definida en G.

Las reglas de construcción se resumen en la Tabla 3. La Regla 5 relaciona "traverses" entre estado finales e iniciales en M con strings derivables a partir de ∑ en G, lo cual surge de lo establecido por la proposición 4. La Regla 6 brinda la posibilidad de que G genere el string vacío λ, si es que λ es aceptado por M. Tabla 3 Dado: Un APDP M = (Q, S, U, P, Ι, F)

Page 15: Unidad 3.pdf

Construir: La gramática libre de contexto G = (N, S, P, ∑) tal que L(G) = L(M)

Sean: B = Ι ∪ { q' / q] write (u,q') está en M para algún q ∈ Q y algún u ∈ U } N = { N(q,q') / q ∈ B, q' ∈ Q }

Las producciones de G son las siguientes:

Regla Si M tiene luego G tiene

1 q] scan (s,q') N(q,q') → s q ∈ B

2 q"] scan (s,q') N(q,q') → N(q,q")s q ∈ B

3 q"] write (u,p) p'] read (u,q') N(q,q') → N(q,q")N(p,p') q ∈ B

4 q] write (u,p) p'] read (u,q') N(q,q') → N(p,p') q ∈ B

5 q ∈ Ι, q' ∈ F ∑ → N(q,q')

6 Ι ∩ F ≠ ∅ ∑ → λ

También es posible obtener una gramática libre de contexto a partir de un procedimiento análogo al uso de

conjuntos finales para el caso de gramáticas regulares. En este caso los conjuntos "traverse" de interés son aquellos en los cuales el estado terminal está en el conjunto:

E = F ∪ { q / q] read (u,q') es una instrucción en M para algún q' ∈ Q y algún u ∈ U } Los símbolos no terminales de la gramática denotan los strings de estos conjuntos "traverse".

4 .4. Ejemplo de Construcción Ejemplo 4: Construir una gramática a partir del APD Mdp que reconoce el lenguaje de doble paréntesis Ldp (Esta es la misma máquina que fuera presentada en el Ejemplo 2).

El estado 1 es el único estado inicial y además el estado sucesor de las dos instrucciones de write . Por lo tanto B = { 1 } y los conjuntos "traverses" de interés son T(1,1), T(1,2), T(1,3), T(1,4) y T(1,5). Dado que el estado 1 de Mdp es además su único estado final, tenemos que L(Mdp) = T(1,1). Por conveniencia emplearemos las siguientes letras mayúsculas simples para los símbolos no terminales:

N(1,1) = S N(1,2) = A N(1,3) = B N(1,4) = C N(1,5) = D Las producciones resultantes de la aplicación de cada regla de la Tabla 3 son las siguientes:

Regla 1: A → ( C → ) B → [ D → ] Regla 2: A → S( C → S) B → S[ D → S]

De la aplicación de la regla 2, las únicas producciones que resultan son aquellas para las cuales q = q" = 1. Regla 3: (1) S → AC (2) S → BD

Las producciones (1) y (2) resultan de tomar p' = 4 y p' = 5 respectivamente. Regla 4: Ninguna Regla 5: ∑ → S

Page 16: Unidad 3.pdf

Regla 6: ∑ → λ

Relación entre las derivaciones de esta gramática y las secuencias de movimientos de Mdp

Secuencia de movimientos para el string de paréntesis [( )]( )

La gramática dada anteriormente puede ser colocada en una forma más entendible: Sustituiremos los no

terminales A, B, C y D en las producciones (1) y (2) por los correspondientes lados derechos de las producciones obtenidas a partir de la aplicación de las reglas 1 y 2. Dado que hay dos alternativas para cada uno de los no terminales, las producciones (1) y (2) se convierten en cuatro producciones cada una, con lo cual la gramática G es:

∑ → λ S → ( ) S → [ ] ∑ → S S → S ( ) S → S [ ] S → ( S ) S → [ S ] S → S ( S ) S → S [ S ]

Esta no es la gramática más simple para Ldp; la siguiente gramática también genera Ldp:

∑ → λ S → ( S ) S → [ S ] ∑ → S S → ( ) S → [ ] S → SS

Sin embargo, esta gramática simple es ambigua, mientras que la gramática obtenida a partir de la Tabla 3 no

lo es (se lo puede garantizar) dado que Mdp opera determinísticamente.

4 .5. Teorema: Para un APD M cualquiera, se puede construir una gramática libre de contexto G, tal que L(G) = L(M).

(Este teorema no se demostrará)

Lenguajes Libres de Contexto

El modelo libre de contexto para lenguajes fue propuesto por lingüistas para entender y caracterizar la estructura de oraciones en lenguajes naturales. A pesar de que este modelo ha influído en el trabajo de investigación que realizan los lingüistas, ha probado ser mucho más útil como modelo para lenguajes de programación que como modelo para lenguajes naturales.

Durante el procesamiento de un programa escrito en un lenguaje de programación, un compilador actúa en dos roles:

1.- Determina si un programa es sintácticamente correcto: Un compilador trata de construir una derivación

(descripción estructural) del programa de acuerdo a una gramática formal; en este rol el compilador actúa como un aceptor.

2.- Produce un programa objeto: El compilador genera instrucciones de máquina a partir de la descripción estructural de un programa fuente; en este rol el compilador actúa como un traductor. Por lo tanto, la elección de un método para realizar la descripción estructural del programa fuente -para realizar

el análisis sintáctico- es un problema central en el diseño de compiladores. La caracterización de sentencias válidas de un lenguaje como el conjunto de strings generado por alguna

gramática libre de contexto ha sido un paso esencial en la aplicación de muchos métodos prácticos de análisis sintáctico.

A continuación desarrollaremos un número de importantes resultados relacionados con Gramáticas Libres de Contexto y los lenguajes que ellas definen.

Comenzaremos con varias transformaciones sobre Gramáticas Libres de Contexto (GLC) que no alteran el lenguaje generado. Estas transformaciones permiten remover elementos no necesarios de la gramática y convertir gramáticas en formas canónicas bien conformadas para análisis sintáctico. Además, probaremos algunos teoremas básicos acerca de la estructura de los lenguajes libres de contexto (LLC). Estos teoremas pueden ser empleados para distinguir entre LLC y lenguajes de estados finitos (LEF), y mostrar que ciertos lenguajes no son libres de contexto.

Page 17: Unidad 3.pdf

Introduciremos algunas definiciones que serán usadas en los futuros temas a desarrollar.

Definición 1: Sea G una gramática libre de contexto. Una producción A → α en la gramática es llamada una regla A de G. La letra no terminal A es la parte izquierda de la regla, y el string α es la parte derecha. En una producción de la forma A → xβ, la letra x (la cual puede ser terminal o no terminal) es llamada "handle" de la producción. Una producción de la forma A → B, donde A y B son no terminales, es llamada producción no generativa de G. Si G permite una derivación

luego la letra no terminal A es recursiva por izquierda en G. Si G permite una derivación

luego A es recursiva por derecha en G. Si G permite una derivación

de uno o más pasos, luego A es un no terminal cíclico de G. 1 . Transformación de Gramáticas

Hasta este punto hemos considerado que dos gramáticas son equivalentes si ellas generan el mismo lenguaje. Sin embargo, en el estudio de la transformación de gramáticas en nuevas formas, uno frecuentemente está interesado en preservar la estructura del lenguaje generado, que está representado por los árboles de derivación de sus sentencias. Por esta razón, no es siempre apropiado considerar dos gramáticas como equivalentes simplemente porque ellas generan el mismo lenguaje.

Puede ocurrir, por ejemplo, que una gramática para un lenguaje permita muchas derivaciones de una sentencia, mientras que otra gramática para el mismo lenguaje permita solamente una sóla derivación de cada sentencia del lenguaje. Desde el punto de vista del análisis sintáctico, las dos gramáticas no pueden considerarse equivalentes: una es ambigua, y la otra no. Para estudiar transformaciones gramaticales aplicables a análisis sintáctico, necesitamos una noción de equivalencia que implique una correspondencia entre derivaciones en gramáticas equivalentes.

1 .1. Equivalencia de Gramáticas

El ejemplo siguiente muestra que dos gramáticas pueden generar el mismo lenguaje, aunque provean estructuras sumamente diferentes a las sentencias del lenguaje.

Page 18: Unidad 3.pdf

Ejemplo 1: Consideremos las siguientes gramáticas G1 y G2:

G1: ∑ → S G2: ∑ → S S → S01 S → S0S S → 1 S → 1

Ambas gramáticas generan el lenguaje L = 1(01)*. Sin embargo, G2 es ambigua, mientras que G1 no lo es. No

deseamos considerar a G1 y G2 como estructuralmente equivalentes, ya que G2 asocia varios árboles de derivación con las sentencias de L, mientras que G1 provee un único árbol de derivación para cada sentencia.

Una definición de equivalencia adecuada para transformaciones gramaticales debe igualar dos gramáticas G1 y G2 solamente si la misma sentencia es no ambigua en cada gramática. Esto se puede conseguir requiriendo que las derivaciones por izquierda de G1 y G2 tengan una relación uno a uno para cada sentencia generada. Este criterio puede ser muy estricto; para una forma de ambigüedad estructural es trivial y fácilmente removible. Ejemplo 2: Sean G1 y G2 dos gramáticas:

G1: ∑ → S G2: ∑ → S S → S01 S → S S → 1 S → S01

S → 1

La gramática G2 tiene todas las producciones de G1 más la regla S → S. Ya que esta regla puede ser aplicada un número arbitrario de veces a cualquier forma sentencial conteniendo S, cualquier string generado por G2 tiene un número infinito de derivaciones distintas. Por el contrario G1 genera cada string mediante una única derivación. A pesar de que G2 es muy ambigua, la única diferencia entre la descripción estructural de una sentencia de acuerdo a las dos gramáticas es el número de repeticiones de ciertas formas sentenciales en cada derivación.

Las derivaciones que contienen repeticiones de formas sentenciales surgen solamente cuando las gramáticas contienen letras no terminales cíclicas. Ya que las letras no terminales cíclicas son no productivas, y desearíamos eliminarlas, nuestro criterio de equivalencia debe permitir la remoción de la ambigüedad asociada a ellas. De acuerdo a ésto, dejamos las derivaciones por izquierda que no contienen formas sentenciales repetidas como proveedoras de las descripciones estructurales correctas de las sentencias generadas por una gramática. Definición 2: Una derivación por izquierda permitida por una GLC es "mínima" si ninguna forma sentencial se repite en la derivación. Definición 3: Dos GLC, G1 y G2, son débilmente equivalentes si L(G1) = L(G2). Ellas son fuertemente equivalentes si son débilmente equivalentes, y , para cada string terminal ω, la derivación por izquierda mínima de ω en G1 puede ser colocada en correspondencia uno a uno con aquellas permitidas por G2.

En gramáticas que no contienen letras no terminales cíclicas cada derivación por izquierda es "mínima"; la equivalencia fuerte depende solamente de la existencia de una correspondencia uno a uno entre derivaciones por izquierda. El siguiente ejemplo ilustra un caso de equivalencia fuerte. Ejemplo 3: Consideremos las gramáticas:

G1: ∑ → A G2: ∑ → A G3: ∑ → A A → a A → a A → a A → aB A → abcA A → aB B → bC B → bC C → cA C → cA

A → abcA

La gramática G2 se obtiene reemplazando las reglas de G1 usadas en la derivación: A ⇒ aB ⇒ abC ⇒ abcA

por la siguiente regla: A → abcA

Claramente, L(G1) = L(G2). Puede construirse una única derivación de un string terminal ω de acuerdo a la gramática G1, a partir de una

derivación de ω de acuerdo a G2 reemplazando cada aplicación de la regla A → abcA con aplicaciones de las reglas:

A → aB B → bC C → cA Por lo tanto G1 y G2 son fuertemente equivalentes. Las producciones de la gramática G3 salen a partir de la unión de las producciones de G1 y G2. A pesar de que

L(G3) = L(G1), G3 no puede ser fuertemente equivalente a G1 o a G2, debido a que G3 es ambigua mientras que G1 y G2 son no ambiguas.

En las próximas secciones, presentaremos cinco transformaciones elementales aplicables a GLC: 1.- Sustitución 2.- Expansión 3.- Remoción de producciones inútiles 4.- Remoción de producciones no generativas 5.- Remoción de letras no terminales recursivas por izquierda

Page 19: Unidad 3.pdf

Para cada transformación, describiremos las condiciones bajo las cuales la gramática transformada es equivalente a la original.

1 .2. Sustitución y Expansión

Una gramática es transformada por sustitución cuando, para algún símbolo no terminal B, en las partes derechas de las reglas que contienen a B, se lo sustituye por las partes derechas de las reglas B. Por ejemplo, supongamos que una gramática libre de contexto G, tiene la producción:

A → ϕBψ donde B ∈ N y ϕ, ψ ∈ (N ∪ T)* y las reglas B

B → β1, ..... , B → βn La transformación de G por sustitución de B en la producción A → ϕBψ, es la gramática G' obtenida de

acuerdo a las siguientes reglas: Regla 1: La producción A → ϕBψ no está en G'. Regla 2: Todas las otras producciones de G están en G'. Regla 3: Cada producción

A → ϕ βi ψ i = 1, 2, ..., n está en G'. Ejemplo 4: Consideremos las gramáticas:

G1: ∑ → S G2: ∑ → S S → ST S → TT T → S S → aSbT T → S T → aSb S → aT T → aSb T → c T → c

La gramática G2 es el resultado de sustituir en la regla S → TT de G1 el T situado más a la izquierda por la

parte derecha de las reglas T. La producción S → TT se omite en G2, pero las reglas T de G1 se conservan. La gramática G2 es fuertemente equivalente a G1. Se puede verificar, por ejemplo, que el string accbcc tiene dos derivaciones por izquierda en cada gramática.

Si una producción introducida por sustitución es un duplicado de una producción en la gramática original, la gramática transformada no será necesariamente fuertemente equivalente a la original. Por ejemplo, si una gramática G tiene las producciones:

A → ϕBψ B → β A → ϕβψ

y sustituímos B en la primera regla A, obtendremos las producciones: A → ϕβψ B → β

en la gramática transformada G'. Mientras G permite dos derivaciones de la forma sentencial ϕβψ desde A: A ⇒ ϕBψ ⇒ ϕβψ A ⇒ ϕβψ

la segunda de estas es la única derivación permitida en G'. Proposición 1: Sustitución. Sea G una gramática libre de contexto, y sea G' generada a partir de G por sustitución de alguna letra no terminal B en una producción A → ϕBψ de G. Si ninguna producción de G' introducida por la Regla 3 de sustitución ha sido previamente introducida a través de la Regla 2, luego G y G' son fuertemente equivalentes. Esta proposición no se demostrará.

La operación inversa a la sustitución es la expansión; supongamos que reemplazamos una producción de la forma:

A → ϕψ donde ϕ, ψ ∈ (N ∪ T)* - λ en una gramática libre de contexto con:

A → Xψ X → ϕ ó A → ϕX X → ψ

en donde X es un nuevo símbolo no terminal. En este caso, hemos expandido la regla A → ϕψ. Si una gramática G' es obtenida a partir de G por expansión de la regla A → ϕψ, luego G puede ser obtenida a partir de G' por sustitución de X en la regla A → Xψ o A → ϕX. Por lo tanto, la proposición 1 y la simetría de equivalencia fuerte muestran que G y G' son fuertemente equivalentes. Proposición 2: Expansión. Si la gramática G' se forma a partir de la gramática libre de contexto G por expansión de alguna regla de G, luego G y G' son fuertemente equivalentes.

1 .3. Producciones Inútiles y Test de Vacío

Una gramática puede contener producciones y no terminales que no son útiles ya que no aparecen en la derivación de ningún string terminal.

Page 20: Unidad 3.pdf

Definición 4: Sea G = (N, T, P, ∑) una GLC cualquiera. Una producción A → α de G es útil si G permite una derivación:

De otra forma A → α es inútil. Un no terminal de G es útil si es la parte izquierda de una producción útil; de

otra forma, es un no terminal inútil.

Una producción útil de una GLC G puede ser identificada mediante la aplicación de dos procedimientos de marcado.

El procedimiento de marcado T, realizado en primer lugar, marca cada regla de G con el símbolo T sólo si hay una derivación de un string terminal usando la regla en el primer paso. El conjunto de las producciones marcadas con T se denomina PT. En términos de PT, el conjunto NT de no terminales conectados a terminales es:

NT = { A ∈ N ∪ { ∑ } / hay una regla A _→ α en PT } Obviamente, cada producción útil de G debe estar en PT, y cada símbolo no terminal útil debe estar conectado

a un terminal. Sin embargo, el hecho de que una producción esté en PT o un símbolo esté en NT no es en sí mismo suficiente para asegurar que el símbolo o la producción sean útiles; un no terminal A en NT o una producción A _→ α en PT no son útiles a menos que una forma sentencial conteniendo el símbolo A pueda ser derivado a partir de ∑. Para determinar si esa forma puede ser derivada, realizaremos un segundo procedimiento de marcado.

El procedimiento de marcado ∑ marca cada regla A → α en PT con el símbolo ∑ si G permite la derivación:

usando solamente reglas contenidas en PT. En tal caso, G debe permitir una derivación:

ya que cada no terminal en ϕ o en ψ está conectado en T. Si denominamos PΣ al conjunto de producciones marcadas con ∑, luego PΣ contiene las producciones útiles de la gramática.

Los procedimientos de marcado con T y marcado con ∑ se llevan a cabo de la siguiente forma:

Procedimiento de marcado con T: Sea G una gramática libre de contexto con producciones P. Construímos una secuencia P0, P1, ... de

subconjuntos de P y una secuencia N0, N1, ... de subconjuntos de N de acuerdo al siguiente algoritmo: 1.- Sean P0 y N0 conjuntos vacíos. Sea i = 0. 2.- Sea Pi+1 = { A → α en P / α está en (Ni ∪ T)* } 3.- Sea Ni+1 = { A ∈ N / Pi+1 contiene una regla A → α } 4.- Si Pi+1 ≠ Pi, sea i = i+1, y luego ir al paso 2. 5.- Sea PT = Pi+1 y NT = Ni+1.

Cada conjunto Pi contiene los no terminales de G a partir de los cuales se puede derivar un string terminal en i pasos. Dado que Pi ⊆ Pi+1 ⊆ P para cada i, el procedimiento terminará luego de un número de pasos no mayor que el número de reglas en G. Dado que α ∈ (Ni ∪ T)*, cuando i = 0, obviamente N0 = ∅, lo cual implica que necesariamente deberá cumplirse que α ∈ T*, o sea α es un string de símbolos terminales.

Procedimiento de marcado con ∑: Sea G una GLC, y sea PT el conjunto de producciones marcadas con T. Construir una secuencia Q1, Q2, ... de

subconjuntos de PT de acuerdo al siguiente algoritmo: 1.- Sea Q1 = { ∑ → α en PT }. Sea i = 1. 2.- Sea Qi+1 = Qi ∪ { A → α en PT / Qi contiene una regla B → ϕAψ } 3.- Si Qi+1 ≠ Qi, sea i = i+1, y luego ir al paso 2. 4.- Sea PΣ = Qi+1.

Cada conjunto Qi contiene una producción A → α sí y sólo sí G permite una derivación:

en no más de i pasos, usando solamente producciones que están en PT. Dado que Qi ⊆ Qi+1 ⊆ PT este

procedimiento se detiene luego de un número de etapas no mayor que el número de producciones en PT. Hemos visto que una producción es útil si la misma está en PΣ luego de que los procedimientos de marcado se

han terminado. Dado que los procedimientos de marcado T y ∑ terminan siempre en un número finito de etapas, podemos establecer la siguiente proposición: Proposición 3: Sea G una gramática libre de contexto con producciones P. Para cada producción en P, es posible decidir si la producción es o no útil en G. Ejemplo 5:

Reglas Marcación G: ∑ → S (1) T ∑

S → AB (2) S → aS (3) T ∑ S → a (4) T ∑ B → Sb (5) T

Page 21: Unidad 3.pdf

C → aC (6)

Mediante la aplicación del procedimiento de marcado T, marcamos las producciones en el orden 4, 1, 3 y 5.

Los no terminales conectados con T son NT = { ∑, S, B } El procedimiento de marcado ∑ marca la regla 1, y luego las reglas 3 y 4. Luego, las reglas útiles de G son:

PΣ = { ∑ → S, S → aS, S → a } La regla 5 es un ejemplo de una regla inútil cuya parte izquierda está conectada con T. Si bien B es derivable a

partir de ∑ mediante ∑ ⇒ S ⇒ AB, la regla 5 es inútil porque A no está conectada con T, y por lo tanto ningún string terminal se deriva a partir de AB.

El procedimiento de marcado T nos dá una forma de verificar si L(G,A) es vacío para algún A que pertenezca a N ∪ { ∑ }. Por cierto, L(G,A) es no vacío sí y sólo sí A está en NT. Es decir, tenemos el siguiente resultado: Teorema 1: Test de Vacío. Para cualquier GLC G uno puede decidir si L(G,A) = ∅ para cualquier símolo no terminal A perteneciente a G. En particular, uno puede decidir si la gramática genera o no strings (Es decir, si L(G) = L(G,∑) = ∅ ).

Es trivialmente cierto que al borrar las producciones inútiles de una gramática obtendremos una nueva gramática que es fuertemente equivalente a la original. Proposición 4: Remoción de Producciones Inútiles. Si la gramática G' se obtiene a partir de una GLC G mediante la remoción de producciones inútiles, luego G y G' son fuertemente equivalentes.

1 .4. Reemplazo de Producciones No Generativas

La remoción de las producciones no generativas de una GLC también elimina los no terminales cíclicos y es una etapa importante en la conversión de una gramática en forma canónica. El procedimiento es similar al que se emplea para remover las transiciones λ de un aceptor de estados finitos. Se empleará la siguiente gramática para ilustrar el principio empleado.

Sea G la gramática con producciones:

∑ → A A → B A → aB ∑ → B A → C A → b

B → C C → Aa C → B

y consideremos las secuencias de pasos de derivación consecutivos no generativos de G. Estas secuencias pueden ser representadas por un Aceptor de Estados Finitos M(G) que tiene un estados por cada no terminal que aparece en una producción no generativa de G, como se muestra en la siguiente figura:

Figura 1: AEF que representa las producciones no generativas de la gramática G

Cada una de las transiciones λ corresponde a una producción no generativa de G, y los estados iniciales y

finales corresponden a los no terminales en los cuales comienza y termina una serie de pasos no generativos. Por ejemplo, M(G) tiene un paso o camino desde el estado inicial A al estado final C por medio de la secuencia de estados:

A → B → C → B → C Esto indica que la siguiente secuencia de pasos:

ϕAψ ⇒ ϕBψ ⇒ ϕCψ ⇒ ϕBψ ⇒ ϕCψ puede ocurrir en una derivación permitida en G, tal que la secuencia es precedida y seguida de una serie de pasos generativos. Este es el caso de la derivación:

∑ ⇒ A ⇒ B ⇒ C ⇒ B ⇒ C ⇒ Aa ⇒ ba que permite derivar el string terminal ba.

En general, si G = (N, T, P, ∑) es una GLC con reglas no generativas, el aceptor de estados finitos asociado es M(G) = (Q, ∅, P', I, F) donde

Q = { A / A → B o B → A es una regla de G, para algún A,B ∈ N } λ

P' = { A → B / A → B es una regla de G } Ι = { A ∈ Q / ∑ → A es una regla de G, o B → ϕAψ es una regla de G con ϕψ ≠ λ } F = { A ∈ Q / A → β es una regla de G con β ∉ N }

Debemos notar que los conjuntos Ι y F incluyen todas las letras no terminales en las cuales pueden empezar o terminar, respectivamente, secuencias de pasos de derivación no generativas ("incluyendo también la secuencia vacía").

Se puede obtener una gramática equivalente a G, pero sin producciones no generativas, reescribiendo cada producción generativa como varias producciones que incorporan el efecto de los pasos de derivación no generativos que son permitidos por G. La nueva gramática G' = (N', T, P", ∑) incluye una nueva letra no terminal por cada

Page 22: Unidad 3.pdf

secuencia de pasos no generativos que puede ocurrir como parte de alguna derivación mínima en G. Cada una de estas secuencias se corresponde con un camino libre de ciclos en M(G) desde algún estado inicial X hasta algún estado final Y:

N' = N ∪ { [X1 X2 ... Xn] X1=X ∈ Ι, Xn=Y ∈ F, X i ≠ X j para i ≠ j, λ λ λ

y X1 → X2 → ... → Xn en M(G) }

Las reglas de la nueva gramática se diseñan de tal forma que cada vez que la secuencia de pasos de derivación:

A ⇒ ϕXψ ⇒ ... ⇒ ϕYψ ⇒ ϕβψ es permitida en una derivación mínima de G, la secuencia:

A ⇒ ϕ[X ... Y]ψ ⇒ ϕβψ es permitida en G'. Las producciones de G' están dadas por las siguientes reglas de construcción: Regla 1: Si una producción de G no contiene símbolos correspondientes a un estado en Q, esa producción es una producción de G'. Regla 2: Si G contiene una producción generativa:

A → α 0 B1 α 1 ... Bk α k k ≥ 1 en la cual { Bi / 1 ≤ i ≤ k } son los símbolos de la parte derecha de la producción correspondientes a miembros de Q; luego G' contiene cada producción:

U → α 0 V1 α 1 ... Vk α k tal que:

Ejemplo 6: Consideremos la gramática G, que da lugar a la máquina M(G) vista en la figura anterior. Si nos referimos a esa figura, vemos que los nuevos no terminales que son requeridos son:

{ [AA], [AC], [BC], [ABC] }

Siguiendo las reglas de construcción vemos que no retenemos ninguna regla de G en la nueva gramática, debido a que en cada regla aparece algún no terminal que se corresponde con algún estado de Q. Luego, las producciones de G' resultantes de la aplicación de la Regla 2 son las siguientes:

Dada cualquier derivación mínima permitida por G, podemos construir una derivación correspondiente de acuerdo a G'. Si reemplazamos cada secuencia (incluyendo la secuencia vacía) de pasos no generativos con un solo paso que identifique la secuencia reemplazada. Por ejemplo, G permite la derivación:

que contiene tres secuencias de pasos no generativos (la última vacía), separadas en cada caso por un paso generativo. La derivación correspondiente de acuerdo a G' es:

∑ ⇒ [BC] ⇒ [ABC]a ⇒ [AA]aa ⇒ baa

La correspondencia de derivaciones ilustrada en este ejemplo se aplica a las gramáticas G y G' toda vez que G' es obtenida a partir de G de acuerdo a las reglas dadas. Proposición 5: Para cualquier gramática libre de contexto G podemos construir una gramática fuertemente equivalente G' que no contenga producciones no generativas.

1 .5. Gramáticas Bien Conformadas

Nuestra habilidad para remover producciones "inútiles" y no generativas de una gramática libre de contexto motiva la siguiente definición: Definición 5: Una GLC G = (N, T, P, ∑) es "bien conformada" si cada producción tiene una de las siguientes formas:

Page 23: Unidad 3.pdf

y donde cada producción es útil.

La ausencia de producciones no generativas en una gramática bien conformada asegura que cada derivación por izquierda que dicha gramática permita sea mínima.

Ahora supongamos que G es una GLC arbitraria. A partir de la Proposición 2 sabemos que podemos expandir todas las reglas ∑ en la forma ∑ → B y obtener una gramática fuertemente equivalente a la original. Por medio de la Proposición 5 podemos transformar esta gramática en una gramática sin reglas no generativas, fuertemente equivalente con la original. En base a la Proposición 4, las reglas "inútiles" pueden ser omitidas, produciéndose una gramática fuertemente equivalente a la original. Por lo tanto, podemos enunciar el siguiente teorema: Teorema 2: Dada una GLC G cualquiera, es posible construir una gramática bien conformada que sea fuertemente equivalente a G.

En una gramática bien conformada, cada producción que sea distinta de las reglas ∑, tiene la siguiente forma: A → α, donde | α | > 1 o α ∈ T* - λ

Por lo tanto, cada paso de derivación, que no sea el primero, incrementa la extensión de la forma sentencial o

genera al menos una letra del string terminal. De aquí se infiere que la derivación de un string terminal de longitud n requiere como máximo 2n pasos de derivación. Estas derivaciones consisten de un paso inicial, de un máximo de n-1 pasos que generan n instancias de no terminales y de un máximo de n pasos que reemplazan los no terminales con símbolos terminales. 2 . Formas Canónicas de Gramáticas

Mediante la introducción de condicionamientos en la forma de las producciones permitidas, es posible definir formas restringidas de gramáticas libres de contexto. Si a partir de una gramática libre de contexto arbitraria podemos obtener una gramática fuertemente equivalente con la original, y además ésta tiene una determinada forma restringida, la misma será una forma canónica de la gramática libre de contexto. Las formas canónicas de gramáticas libres de contexto son útiles por dos razones: (1) Generalmente es más fácil establecer ciertas propiedades en las formas canónicas de las gramáticas, que en las GLC en general. (2) La representación de LLC mediante gramáticas en forma canónica puede facilitar la generación de métodos de análisis sintáctico de los lenguajes.

Las gramáticas bien conformadas constituyen formas canónicas de GLC que simplifican el estudio mediante la remoción de producciones inútiles y no generativas. Otras dos formas canónicas de gran importancia son: (1) la gramática en forma normal, que es particularmente útil para el trabajo teórico debido a que las producciones se restringen a un número muy pequeño de formas simples; (2) la gramática en forma stándard, que es una forma útil para el análisis sintáctico ya que no permite recursión por izquierda.

Es importante notar que la forma de Backus-Naur (FBN) empleada frecuentemente para representar la sintaxis de los lenguajes de programación no es una forma canónica de GLC; ésta es simplemente una notación alternativa para las producciones de una GLC no restringida.

2 .1. Gramáticas en Forma Normal (GFN) Definición 6: Una GLC G = (N, T, P, ∑) está en forma normal si cada producción tiene una de las siguientes formas:

Esta forma canónica es también llamada "Forma Normal de Chomsky".

Dado que esta forma no contiene producciones no generativas, una gramática en forma normal será bien

conformada si es que no contiene producciones inútiles. El principio empleado para transformar una GLC G en una gramática con forma normal, se ilustra en la próxima figura. De acuerdo al Teorema 2, no hay restricción para asumir que G es bien conformada. Por lo tanto, cada producción de G tiene una de las siguientes formas:

Page 24: Unidad 3.pdf

Figura 2: Conversión a la Forma Normal

Aplicando la Proposición 2 a G podemos construir una gramática G' expandiendo cada regla

A → α, con | α | > 2, en un conjunto de reglas cuya parte derecha contiene dos símbolos. Por cada regla: A → X 1 X 2 ... X k k > 2, X i ∈ N ∪ T

introducimos k-2 nuevos no terminales: [X 2 ... X k ], [X 3 ... X k ], ..., [X k-1 X k ]

y reemplazamos: A → X 1 X 2 ... X k

con el conjunto de producciones: A → X 1 [ X 2 ... X k ]

[ X 2 ... X k ] → X 2 [ X 3 ... X k ] ....

[ X k-1 X k ] → X k-1 X k

La aplicación de la producción A → X 1 X 2 ... X k en G se corresponde con la aplicación de estas nuevas producciones en secuencia, tal como se muestra en la figura anterior. Cada regla de G' se encuentra ahora en una de las siguientes formas:

Las producciones en la columna izquierda están en forma normal, pero aquellas en la columna derecha no se

encuentran en esa forma. Estas producciones pueden ser colocadas en forma normal por medio de la introducción de un no terminal [x] por cada letra terminal x en la gramática, y reemplazando cada ocurrencia de x en las producciones por [x]. La regla [x] → x es luego incorporada a G'. Por ejemplo, una producción de la forma A → bC se convierte en la producción A → [b]C con la regla [b] → b incluída en la gramática.

De acuerdo a la Proposición 2 las alteraciones que hemos descripto producen una nueva gramática fuertemente equivalente a la original. Por lo tanto, tenemos el siguiente teorema: Teorema 3: (Teorema de la Forma Normal) A partir de una GLC cualquiera, uno puede construir una gramática fuertemente equivalente en forma normal. Ejemplo 7: Consideremos la siguiente gramática bien conformada:

G: ∑ → λ B → bCb ∑ → A A → a A → ABBA C → c

Aplicando el procedimiento ilustrado en la figura 2 obtenemos la siguiente gramática:

G': ∑ → λ B → b[Cb] ∑ → A [Cb] → Cb A → A[BBA] A → a

[BBA] → B[BA] C → c [BA] → BA

Cada producción de G' se encuentra bajo la forma normal con la excepción de las siguientes reglas:

[Cb] → Cb B → b[Cb]

Pero mediante el reemplazo de las mismas con las siguientes reglas: [Cb] → C[b] B → [b][Cb]

[b] → b obtenemos una gramática con FORMA NORMAL fuertemente equivalente a G.

2 .2. Gramáticas en Forma Stándard (GFS)

Las GLC en forma stándard tienen las letras terminales como los "handles" de todas las reglas. Debido a que no son recursivas por izquierda, éstas juegan un rol muy importante en el análisis sintáctico.

Page 25: Unidad 3.pdf

Definición 7: Una GLC G = (N, T, P, ∑) está en su forma stándard si cada producción tiene una de las siguientes formas:

Esta forma canónica es también llamada "Forma Normal de Greibach".

Dado que esta gramática no puede tener reglas A → B, una gramática en forma stándard es bien conformada si

es que no contiene producciones inútiles En una GFS, cada paso (a excepción del primero) de las derivaciones por izquierda tiene la siguiente forma:

ϕ A ψ ⇒ ϕ a β ψ y genera al menos un símbolo terminal. Por lo tanto, se requieren como máximo n+1 pasos para la derivación de un string terminal de n símbolos. La recursión por izquierda es imposible en una GFS debido a que la gramática no permite ninguna derivación del tipo:

Supongamos que queremos construir una gramática en forma stándard (GFS) a partir de una GLC G arbitraria. Sin perder generalidad podemos asumir que G es bien conformada. Cada producción de G tendrá una de las formas:

Cada uno de estos tipos de producción se encuentra en forma stándard excepto las del tipo

A → Bβ . Por lo tanto, nuestra tarea será reemplazar producciones que tengan "handles" no terminales mediante producciones en forma stándard.

Sea A un símbolo no terminal de G y consideremos una derivación por izquierda a partir de A en la cual en cada paso de derivación se aplica una producción que tiene un símbolo no terminal Xi como su "handle":

A ⇒ X 1 β 1 ⇒ X 2 β 2 β 1 ⇒ ..... X k β k ... β 2 β 1 ⇒ ..... (1) Esta derivación debe ser finita en longitud a menos que algún símbolo no terminal se repita en la secuencia:

A, X 1, X 2, ...., X k, ... Ahora si X i = X j = X para algún i < j, la derivación:

es permitida y X es un símbolo no terminal recursivo por izquierda en G. Por otro lado, si G no tiene símbolos no terminales recursivos por izquierda, cada derivación de la forma (1) producirá una forma sentencial que comienza con una letra terminal en un número finito de pasos. Un número finito de sustituciones convertirán las reglas de G en forma stándard. Ejemplo 8: Transformaremos la siguiente gramática G1 en una gramática en forma stándard:

G1: ∑ → A A → Ba B → Cb C → cC A → Ca B → CAb C → c

Esta gramática no tiene símbolos no terminales recursivos por izquierda (C es recursivo por derecha). Usando

la Proposición 1 (Transformación de sustitución) podemos transformar a G1 mediante la sustitución de los "handles" correspondientes a las reglas A:

A → Ba se transforma en A → Cba y A → CAba A → Ca se transforma en A → cCa y A → ca

El resultado es la gramática G2:

G2: ∑ → A A → Cba B → Cb A → CAba B → CAb A → cCa C → cC A → ca C → c

Las reglas B son ahora inútiles y pueden ser borradas. Dos de las nuevas reglas A no están en forma stándard,

pero una segunda sustitución completa la conversión. El resultado es la gramática G3: G3: ∑ → A A → cCba A → cba

A → cCAba A → cAba A → cCa C → cC A → ca C → c

La construcción de la gramática G3 a partir de G1 satisface la condición de la Proposición 1: "Ninguna

producción transformada duplica una producción previamente existente en la gramática". Por lo tanto G3 es fuertemente equivalente a G1.

La figura siguiente muestra bajo la forma de un árbol las sustituciones empleadas en la construcción de G3. Los caminos en el árbol representan derivaciones por izquierda desde A en G1, que contienen al menos una aplicación de una producción con un "handle" no terminal, y finalizando con la aplicación de una producción en forma stándard:

Page 26: Unidad 3.pdf

Figura 3: Conversión de Producciones a Forma Stándard por Sustitución

Los reemplazos de las reglas A con producciones en forma stándard son equivalentes a introducir en la nueva

gramática reglas A cuya parte derecha son las formas sentenciales que rotulan las hojas del árbol.

Si la gramática a ser convertida en una GFS tiene símbolos no terminales recursivos por izquierda, el procedimiento empleado en el Ejemplo 8 nunca terminará. En el siguiente ejemplo ilustraremos el principio usado en la eliminación de no terminales recursivos por izquierda. Ejemplo 9: Consideremos la siguiente gramática:

G1: ∑ → A A → AbA A → a la cual tiene la producción recursiva por izquierda A → AbA. Por simplicidad, llamaremos A al conjunto L(G,A). De acuerdo a la gramática, A debe satisfacer la ecuación:

A = A(bA) ∪ a X = XP ∪ Q Sabemos que es posible aplicar la regla de Arden para obtener una expresión alternativa para A:

A = a(bA)* X = QP* = a ∪ a bA (bA)*

A partir de esta expresión, es sencillo encontrar producciones en forma stándard que generan los strings en A. El conjunto Z = bA (bA)* es generado por las producciones:

Z → bAZ Z → bA en donde Z es un nuevo símbolo no terminal. Debido a que la clausura se realiza a través de recursión por derecha en lugar de recursión por izquierda, Z no aparece como un "handle" en estas reglas; en realidad éstas se encuentran en forma stándard.

El conjunto: A = a ∪ a Z

es generado por las reglas A: A → aZ A → a

Por lo tanto la gramática: G2: ∑ → A A → aZ Z → baZ

A → a A → bA describe el mismo lenguaje que G1. Más aún, cada derivación en G1 que usa recursión por izquierda sobre A:

∑ ⇒ A ⇒ AbA ⇒ AbAbA ⇒ ... ⇒ a(bA)k ⇒ ... ⇒ a(ba)k k ≥ 1 se corresponde unívocamente con la derivación:

∑ ⇒ A ⇒ aZ ⇒ abAZ ⇒ ... ⇒ a(ba)k-1Z ⇒ a(ba)k-1bA ⇒ a(ba)k k ≥ 1 usando recursión por derecha sobre Z en G2. Esta relación se ilustra con la Figura 4. Por lo tanto, las gramáticas G1 y G2 son fuertemente equivalentes.

Figura 4: Reemplazo de una regla recursiva por izquierda

Para eliminar la recursión por izquierda en algún no terminal X de una gramática arbitraria puede ser necesario

manejar varias reglas X recursivas. Esto requiere una generalización de la transformación ilustrada en el ejemplo anterior. Proposición 6: Sea G una gramática libre de contexto, y supongamos que las reglas X de G son:

X → α 1, ...., X → α n X → Xβ 1, ...., X → Xβ m

en donde el segundo grupo de reglas incluye cada regla X que tiene a X como su "handle". Sea G' la gramática obtenida al reemplazar las reglas X de G con las reglas:

X → α 1, ...., X → α n X → α 1Z, ...., X → α nZ Z → β 1, ...., Z → β m

Page 27: Unidad 3.pdf

Z → β 1Z, ...., Z → β mZ

en donde Z es un símbolo no terminal nuevo. Luego G y G' son fuertemente equivalentes. Prueba: El conjunto de strings representados por X en G es:

X = (α 1 ∪ .... ∪ α n) ∪ X (β 1 ∪ .... ∪ β m) donde α i y β j representan los conjuntos de strings derivables a partir de α i y β j . Aplicando la regla de Arden { X = Q ∪ XP ⇒ X = QP* }, encontramos que:

X = (α 1 ∪ .... ∪ α n) (β 1 ∪ .... ∪ β m)*

El conjunto de strings representados por X en G' es: X = (α 1 ∪ .... ∪ α n) ∪ (α 1 ∪ .... ∪ α n) Z

en donde: Z = (β 1 ∪ .... ∪ β m) ∪ (β 1 ∪ .... ∪ β m) Z

Resolviendo la segunda de estas ecuaciones para Z mediante el empleo de la regla de Arden { X = Q ∪ PX ⇒ X

= P*Q }, y sustituyendo el resultado en la primera ecuación, resulta: X = (α 1 ∪ .... ∪ α n) ∪ (α 1 ∪ .... ∪ α n) (β 1 ∪ .... ∪ β m)* (β 1 ∪ .... ∪ β m) = = (α 1 ∪ .... ∪ α n) (β 1 ∪ .... ∪ β m)*

Por lo tanto X representa los mismos strings en cada gramática, y dado que solamente las reglas X de G han

sido reemplazadas, podemos concluir que L(G') = L(G). Además G y G' son fuertemente equivalentes debido a que las derivaciones:

X ⇒ Xβ j1 ⇒ ..... ⇒ Xβ jk ... β j1 ⇒ α i β jk ... β j1 en G, están en correspondencia uno a uno con derivaciones de la forma

X ⇒ α i Z ⇒ α i β jk Z ⇒ ..... ⇒ α i β jk ... β j1 en G'.

La Proposición 6 forma la base de un procedimiento para convertir cualquier GLC bien conformada en una gramática en forma stándard equivalente. La descripción del procedimiento se ve simplificada por la introducción de la noción de subgramáticas. Definición 8: Sea G una GLC, y sea M un subconjunto de los símbolos no terminales de G. La subgramática G(M) tiene el siguientes conjunto de producciones:

{ A → α / A ∈ M y A → α es una regla de G } Por lo tanto G(M) contiene cada regla de G cuya parte izquierda pertenece a M.

Supongamos que G es una gramática bien conformada. Sea M un conjunto de símbolos no terminales tal que

ningún elemento de M es recursivo por izquierda en la subgramática G(M). Claramente, M no puede contener un símbolo no terminal A si existe una regla A → Aα en M. Sin embargo, M puede contener símbolos no terminales que son recursivos por izquierda en G. Ejemplo 10: Sea G la gramática:

G1: ∑ → A A → Ba B → Ab A → a

A y B son recursivos por izquierda. Sin embargo, A no es recursivo por izquierda en la subgramática: G({A}) = { A → Ba , A → a }

y B no es recursivos por izquierda en la subgramática: G({B}) = { B → Ab }

Consideremos ahora una gramática G bien conformada conteniendo símbolos no terminales recursivos por

izquierda (SNRI). Deseamos poner a G en una forma stándard. Sea G(M) una subgramática de G en la cual ningún símbolo no terminal es recursivo por izquierda, y sea X algún símbolo no terminal que es recursivo por izquierda en G. Nuestro procedimiento transformará G en una gramática equivalente G' tal que G'(M ∪ {X}) no tiene SNRI. Repitiendo este procedimiento por cada SNRI en G, eliminaremos toda recursión por izquierda en la gramática. Luego se puede emplear sustitución para reescribir producciones que no se encuentran en forma stándard.

El procedimiento de eliminación de recursión por izquierda en X consta de dos pasos: 1.- Usamos sustitución para transformar las reglas X de G de tal forma que sus "handles" no sean miembros de M. 2.- Usamos la Proposición 6 para reemplazar cualquier regla X recursiva por izquierda. Paso 1: Consideremos cualquier regla X de G tal que su "handle" pretenezca a M:

X → Yβ , Y ∈ M Sean las reglas Y de G:

Y → α 1, ...., Y → α k Usando sustitución, reemplazamos la regla X → Yβ con las nuevas reglas:

X → α 1β, ...., Y → α kβ y repetimos el procedimiento hasta que G no tenga reglas X con "handles" en M.

Por cada sustitución realizada en el transcurso del Paso 1, la Proposición 1 asegura que la gramática transformada genera el mismo lenguaje que la gramática original. Dado que solamente las reglas X son incorporadas a G, el Paso 1 no puede introducir recursión por izquierda a la subgramática G(M). Paso 2: Luego de completado el Paso 1, las reglas X de G son:

Page 28: Unidad 3.pdf

X → α 1, ...., X → α n (reglas en las cuales X no es recursivo por izquierda) X → Xβ 1, ...., X → Xβ m (reglas en las cuales X es recursivo por izquierda)

en las cuales ningún "handle" es un miembro de M. Reemplazamos las reglas X con las reglas: X → α 1, ...., X → α n X → α 1Z, ...., X → α nZ Z → β 1, ...., Z → β m Z → β 1Z, ...., Z → β mZ

en donde Z es un símbolo no terminal nuevo. La Proposición 6 asegura que el reemplazo realizado en el Paso 2 no cambia el lenguaje generado por G.

Solamente las reglas X y Z son adicionadas, por lo tanto todos los símbolos no terminales en M se mantienen no recursivos por izquierda. Dado que X y Z no aparecen como "handles" en ninguna de las nuevas reglas, y que ni X ni Z son recursivos por izquierda, por lo tanto G(M ∪ {X}) no contiene SNRI.

Cuando toda recursión por izquierda haya sido removida de G a través de aplicaciones repetidas de los Pasos 1 y 2, los símbolos no terminales de la gramática serán N ∪ R, en donde R = {Z1,..., Zp} contiene los símbolos no terminales introducidos a través del Paso 2 del procedimiento. La gramática puede contener aún reglas con símbolos no terminales como "handles". Dado que no hay presente recursión por izquierda, estas reglas pueden ser reemplazadas con reglas en forma stándard mediante un número finito de sustituciones. Algunas de las reglas Z introducidas a través del Paso 2 pueden ser no generativas, debido a que alguno de los strings β 1, ...., β m puede consistir de un sólo símbolo no terminal. Sin embargo, sustituyendo sus "handles" obtenemos reglas generativas ya que ningún elemento de R puede ser el "handle" de alguna regla Z, y solamente las reglas Z pueden ser no generativas.

Hemos mostrado como convertir una GLC bien conformada en GLC en forma stándard. Además, las Proposiciones 1 y 6 garantizan que la gramática en forma stándard es fuertemente equivalente a la gramática dada, a menos que en la sustitución de los "handles" introduzcamos reglas preexistentes en la gramática (En este último caso, podemos evitar la duplicación a través del uso de símbolos no terminales adicionales. Por lo tanto, podemos establecer el siguiente teorema: Teorema 4: (Teorema de la Forma Stándard) Si G es una GLC, se puede construir una gramática G' en forma stándard tal que G y G' son fuertemente equivalentes. Ejemplo 11: Ilustraremos la conversión a la forma stándard usando la gramática G1:

G1: ∑ → A A → AaB B → BaC C → c A → B B → C

Los símbolos no terminales A y B son recursivos por izquierda en G1, C no lo es. Por lo tanto, M contiene

inicialmente a C. En primer lugar eliminaremos la recursión por izquierda debida a B. Para ello debemos tomar las reglas B con "handles" en M:

B → C y realizar el reemplazo correspondiente:

B → c Por lo tanto, las reglas B en la gramática serán:

B → BaC B → c

Luego procedemos a ejecutar el Paso 2: B → c B → cZ Z → aC Z → aCZ

en donde Z es un símbolo no terminal nuevo. La nueva gramática es todavía recursiva por izquierda en A, pero M contiene B y C. Luego de sustituir B en la regla A → B, las reglas A de la gramática serán:

A → AaB A → c A → cZ

Luego de aplicar el Paso 2, la primera de estas reglas es reemplazada con las reglas: A → cZ' Z' → aB A → cZZ' Z' → aBZ'

en donde Z' es un símbolo no terminal nuevo. La gramática completa, que ahora se encuentra en forma stándard, es G2:

G2: ∑ → A A → cZ' Z' → aBZ' Z → aC A → c A → cZZ' B → c Z → aCZ A → cZ Z' → aB B → cZ C → c