Algoritmos y Estructuras de Datos Compiladores e Intérpretes Herramientas Lenguaje de programación
!Prog C/C++
Linux Matemáticas
Mates Discretas
Programación Orientada a Objetos Redes y Computación Distribuida Sistemas Operativos

Gramáticas

[date: 13-01-2025] [last modification: 14-01-2025]
[words: 2741] [reading time: 13min] [size: 23296 bytes]

Definición de una gramática y sus diferentes tipos. Derivaciones y los lenguajes que generan. Árboles sintácticos. Las diferentes formas normales de una gramática.

Definición de gramática

Las gramáticas están formadas por 4 componentes:

$$ G = (V, T, P, S) $$

Las letras mayúsculas normalmente denotan variables y las minúsculas símbolos terminales.

Tipos de gramáticas

Ya que tenemos el contexto de lo que es una gramática, y expandiendo lo que se vio en la introducción, diferenciamos varios tipos de gramáticas en función de las restricciones que tenemos a la hora de crear las reglas de producción.

Por tanto:

$$ G3 \subseteq G2 \subseteq G1 \subseteq G0 $$

Gramáticas Regulares (GR)

Los lenguajes regulares pueden asociarse a una gramática de tipo 3 o regular. Por la definición del apartado anterior, tenemos que son:

Lineal por la derecha

Una gramática $ G = (V, T, P, S) $ es lineal por la derecha si todas sus producciones son de la siguiente forma:

$$ A \to xB \\ A \to x$$

Donde $A, B \in V$ (son variables) y $x \in T^*$ (es cualquier cadena de símbolos terminales).

Lineal por la izquierda

Para que sea lineal por la izquierda, todas sus producciones son de la siguiente forma:

$$ A \to Bx \\ A \to x$$

Gramáticas independientes de contexto (GIC)

En cambio, las reglas de las Gramáticas Independientes de Contexto tienen la siguiente forma:

$$ A \to \beta \\ A \in V \\ \beta \in (V + T)^* $$

Es decir,

Gramáticas sensibles al contexto (GSC)

$$ G = (V, T, S, P) $$

Un lenguaje $L$ es sensible al contexto (LSC) si existe una GSC tal que $L = L(G)$ o $L = L(G) \cup \set{\lambda}$.

Los LSC que no contengan $\lambda$ son reconocidos por los Autómatas Linealmente Acotados (ALA).

$$ \text{LSC} \subset \text{LREC} $$

Gramáticas sin restricciones (GSR)

Las GSR generan los LRE.

Análisis de una gramática

Derivaciones de una gramática

Sea $G = (V, T, P, S)$ y $\alpha A \beta$ una cadena de terminales y variables, donde $A$ es variable ($A \in V$) y el resto son variables y terminales ($\alpha, \beta \in (V + T)^*$).

Si existe la producción $A \to \gamma$, entonces:

$$ \alpha A \beta \underset{G}{\implies} \alpha \gamma \beta $$

La implicación sobre la gramática $G$ indica que se utilizan sus reglas de producción para derivar la nueva expresión. Alternativamente, $\overset{*}{\underset{G}{\implies}}$ denota un conjunto de derivaciones (no solo una).

Derivación
Una derivación de una sentencia $w$ es la secuencia de sustituciones de símbolos no terminales según las reglas de producción de la gramática que, partiendo del símbolo inicial $S$, producen como resultado $w$.

Básicamente consisten en expandir las reglas de la gramáticas para llegar a una cadena del lenguaje que define.

Lenguaje de una gramática

Forma sentencial

Cualquier cadena $\alpha$ de variables y símbolos terminales que se derive de su gramática $G$, es una forma sentencial.

Formalmente:

$$ \alpha \in (V + T)^* \enspace / \enspace S \overset{*}{\underset{G}{\implies}} \alpha $$

Es decir, cualquier cadena formada por la gramática desde el axioma es una forma sentencial.

Lenguaje de una gramática

El lenguaje de una gramática $G$, denotado por $L(G)$, está formado por las formas sentenciales de solo símbolos terminales (las que están en $T^*$).

Formalmente:

$$ L(G) = \set{w \in T^* | S \overset{*}{\underset{G}{\implies}} w } $$

A cada $w$ del lenguaje $L(G)$ se le llaman sentencias.

Es decir, cualquier cadena de símbolos terminales derivada del axioma es una sentencia. El lenguaje de la gramática es el conjunto de todas estas sentencias.

Árbol de derivación

Se pueden representar las derivaciones de la gramática en forma de árbol.

Concatenando todas las hojas del árbol de izquierda a derecha, se obtiene la cadena resultado, que se deriva desde la raíz.

Para el ejemplo antes visto: $E \overset{*}{\implies} ‘a * (a + b00)'$

    flowchart TB
    1((E)) --> 2((E))
    1 --> 3((*))
    1 --> 4((E))
    2 --> 5((I)) --> 6((a))
    4 --> 7(("("))
    4 --> 8(("E"))
    4 --> 9((")"))
    8 --> 10((E))
    8 --> 11((+))
    8 --> 12((E))
    10 --> 13((I)) --> 14((a))
    12 --> 15((I))
    15 --> 16((I))
    15 --> 17((0))
    16 --> 18((I)) --> 20((b))
    16 --> 19((0))

Ambigüedad

Ambigüedad
Una gramática $G$ es ambigua si existe al menos una cadena $w$ en $T^*$ para que podemos encontrar dos árboles de derivación diferentes.

La existencia de derivaciones diferentes no supone un problema para la gramática, pero la existencia de varios árboles de derivación diferentes sí.

Estas ambigüedades no están permitidas en los lenguajes formales. No entraremos en mucho detalle, pero es importante tenerla en cuenta.

Formas normales para GIC

Forma normal
Las Gramáticas Independientes de Contexto (GIC) en formas normales pueden generar todos los Lenguajes Independientes de Contexto (LIC).

Veremos las siguientes formas normales:

El principal motivo para usar las formas normales es que no cambian la gramática y reducen la complejidad para la obtención de sus derivaciones, es más fácil de aplicar. Para comprobar determinada GIC en un computador, se tendrán que comprobar todas las reglas para cada símbolo, lo que es un alto coste computacional. Las formas normales son alternativas que intentan arreglar eso.

En general, antes de obtener una gramática en forma normal, es necesario realizar unas transformaciones previas que no modifican el lenguaje generado:

  1. Eliminación de producciones con $\lambda$
  2. Eliminación de producciones unitarias (reglas de encadenamiento de la forma $A \to B, \enspace A,B \in V$)
  3. Eliminación de símbolos inútiles
    1. Eliminar símbolos no generadores
    2. Eliminar símbolos no alcanzables

Eliminación de producciones con $\lambda$

Anulabilidad de una variable
La variable $A$ es anulable si $A \overset{*}{\implies} \lambda $, es decir, si $A$ se puede derivar en la cadena vacía.

Algoritmo para encontrar todos los símbolos anulables:

Algoritmo de eliminación de producciones $\lambda$

Sea $G = (V, T, P, S)$ una GIC.

  1. Se determinan todos los símbolos anulables de $G$ con al algoritmo anterior.
  2. Se construye la gramática $G_1 = (V, T, P_1, S)$, donde $P_1$:
    1. Para cada producción $A \to X_1 \ldots X_k$ donde $k \ge 1$, supongamos que $m$ de ellos son anulables.
    2. La nueva gramática tendrá $2^m$ versiones de esta producción:
      Las $X_i$ anulables están presentes o ausentes en todas las combinaciones posibles
    3. Si $m = k$, no se incluye en caso de que todo $X_i$ esté ausente, sino que aplicará sobre aquellas producciones que hagan uso de $A$ (dado que $A$ es anulable).
    4. Se eliminan las producciones de la forma $A \to \lambda$.
    5. Si $\lambda$ forma parte del lenguaje, se añade la producción $S \to \lambda$. Este es el único lugar donde $\lambda$ está admitido.

Eliminación de producciones unitarias

Producción unitaria

Una producción unitaria es aquella de la forma:

$$ A \to B $$

donde $A$ y $B$ son variables ($A, B \in V$).

Pares unitarios
Dos variables $A$ y $B$ forman un par unitario si y solo si $A \overset{*}{\implies} B$ mediante solo producciones unitarias.

Son importantes eliminarlas porque no cambian nada (solo renombran variables) y añaden pasos extra en las derivaciones.

Obtención de los pares unitarios:

Algoritmo de eliminación de producciones unitarias

Dada una gramática GIC $G = (V, T, P, S)$, se construye la gramática $G_1 = (V, T, P_1, S)$, donde $P_1$:

  1. Se encuentran todos los pares unitarios con el algoritmo anterior.
  2. Para cada par unitario $(A, B)$, añadimos a $P_1$ todas las producciones $A \to \alpah$ donde $B \to \alpha$ es una producción no unitaria.

Eliminación de símbolos inútiles

Símbolos útiles

Un símbolo $X$ es útil para una gramática $G$ si existe alguna derivación de la forma:

$$ S \overset{*}{\implies} \alpha X \beta \overset{*}{\implies} w $$

donde $w$ sea una sentencia (está en $T^*$).

Es decir, un símbolo es útil si la gramática lo utiliza para generar sentencias.

Símbolos generadores
Un símbolo $X$ es generador si $X \overset{*}{\implies} w$ y $w$ es una sentencia (está en $T^*$).
Nótese que todo símbolo terminal es generador.
Símbolos alcanzables
Un símbolo $X$ es generador si existe una derivación $S \overset{*}{\implies} \alpha X \beta$ para algún símbolo $\alpha$ y $\beta$.
Teorema
Todo símbolo útil es generador y alcanzable.

Es decir, un símbolo es generador cuando a partir de él puedo derivar sentencias; mientras que un símbolo alcanzables es aquel al que se puede llegar desde el axioma.

Algoritmo para calcular símbolos generadores:

Algoritmo para calcular símbolos alcanzables:

Una vez determinados los símbolos útiles, se eliminan los que no lo son.

Forma Normal de Chomsky (FNC)

Se dice que una gramática $G$ está en la Forma Normal de Chomsky (FNC) si todas sus producciones tienen la forma:

Forma normal de Chomsky
$$ \begin{align*} A \to& \; BC \\ A \to& \; a \\ S \to& \; \lambda \\ \end{align*} $$

Donde $A, B, C \in V$ (variables), $a \in T$ (terminal) y $S$ es el axioma.

Características:

Para convertir una gramática a FNC:

  1. Eliminar las producciones con $\lambda$, las producciones unitarias y símbolos inútiles.

  2. Luego, conseguir que en los cuerpos de longitud 2 o más solo aparezcan 2 variables.

    1. Para cada símbolo terminal $a$ que aparece en un cuerpo de longitud 2 o más, se crea una nueva variable $A$.
    2. $A$ solo tiene la producción $A \to a$.
    3. Se sustituyen las apariciones de $a$ por $A$
  3. Descomponer los cuerpos de longitud 3 o más en una cascada de producciones en cuyos cuerpos sólo aparezcan 2 variables.

    1. Se descomponen las producciones de forma $A \to B_1 \ldots B_k$ para $k \ge 3$, con un grupo de variables en cada cuerpo.
    2. Se introducen $k - 2$ variables nuevas: $C_1, \ldots C_{k-2}$.
    3. Se reemplaza la producción original por las $k - 1$ producciones:
      • $A \to B_1 C_1$
      • $C_1 = B_2 C_2$
      • $C_{k-3} \to B_{k-2} C_{k-2}$
      • $C_{k-2} \to B_{k-1} B_{k}$

Forma Normal de Greibach

Todo LIC no vacío es $L(G)$ para alguna gramática $G$ cuyas producciones tienen la forma:

$$ A \to a \alpha $$

donde $a$ es un símbolo terminal y $\alpha$ es una cadena de 0 o más variables.

El uso de una producción introduce un símbolo terminal en una forma sentencial.

==> Es más eficiente que Chomsky, pero tiene la misma complejidad (lineal).

Anterior: Lenguajes Regulares Volver a Compiladores e Intérpretes Siguiente: Autómatas de Pila