Compiladores Ejercicios

Descargar como pdf o txt
Descargar como pdf o txt
Está en la página 1de 9

Ejercicios Resueltos.

2.2.4 Construya gramáticas libres de contexto no ambiguas para cada uno de los siguientes lenguajes.
En cada caso muestre que su gramática es correcta.
a) Expresiones aritméticas en notación prefija.
En esta gramática, tenemos reglas que permiten la construcción de expresiones aritméticas en
notación prefija. La regla principal es S, que puede ser un operador seguido de dos subexpresiones
(S S) o un número (num) o un identificador (ident).
La derivación comienza con la regla S y se aplican las reglas de producción para generar la expresión
deseada.
Por ejemplo, podemos generar la expresión "+ * 3 4 5" a partir de la regla S => + S S => + * S S S =>
+ * num num num.
R = S -> + S S | - S S | * S S | / S S | num | ident
Para demostrar que esta gramática es correcta, podemos usar una derivación para generar una
expresión aritmética en notación prefija:
S => + S S
=> + * S S S
=> + * num num num
b) Listas asociativas por la izquierda de identificadores separados por comas.
Esta gramática permite la construcción de listas de identificadores separados por comas donde la
asociatividad es por la izquierda.
La regla principal es S, que puede ser un identificador (id) o un identificador seguido de una coma y
otra lista (S , id).
Por ejemplo, podemos generar la lista "a, b, c" a partir de la regla S => S , id => S , id , id => id , id ,
id.
R = S -> id | S , id
Para demostrar que esta gramática es correcta, podemos usar una derivación para generar una lista
asociativa por la izquierda:
S => S , id
=> S , id , id
=> id , id , id
d) Expresiones aritméticas de enteros e identificadores con los cuatro operadores binarios +, −, *, /.
En esta gramática, tenemos reglas que permiten la construcción de expresiones aritméticas con
números enteros y/o identificadores usando los operadores binarios +, -, *, y /.
La regla principal es S, que puede ser una expresión más otra expresión con un operador (S + S) o (S
- S) o (S * S) o (S / S), o simplemente un número (num) o un identificador (ident).
Por ejemplo, podemos generar la expresión "3 + a * 5" a partir de la regla S => S + S => num + S =>
num + S * S => num + ident * num.
R = S -> S + S | S - S | S * S | S / S | num | ident
2.2.5 a) Muestre que todas las cadenas binarias generadas por la siguiente gramática tienen valores
que pueden dividirse entre 3. Sugerencia: Use la inducción en el número de nodos en un árbol de
análisis sintáctico.
num => 11 | 1001 | num 0 | num num
Para mostrar que todas las cadenas binarias generadas por la gramática pueden dividirse entre 3,
podemos utilizar la inducción en el número de nodos en un árbol de análisis sintáctico. La idea es
demostrar que cada nodo en el árbol de análisis sintáctico representa una cadena binaria que es
divisible por 3.
1. Base de la inducción: Consideremos los casos base de la gramática:
• num -> 11: Aquí, la cadena generada es "11", que en binario representa el número 3. Por
lo tanto, es divisible por 3.
• num -> 1001: La cadena generada es "1001", que en binario representa el número 9.
Este número también es divisible por 3.
2. Hipótesis de inducción: Supongamos que todas las cadenas binarias generadas por un árbol
de análisis sintáctico con hasta n nodos pueden dividirse entre 3.
3. Paso de inducción: Consideremos las reglas de producción restantes:
• num -> num 0: Si la cadena generada por num es divisible por 3 (por hipótesis de
inducción), agregar un "0" al final no cambiará su divisibilidad por 3, ya que multiplicar un
número divisible por 3 por 2 aún da como resultado un número divisible por 3.
• num num: Si las cadenas generadas por ambos num's son divisibles por 3 (por hipótesis
de inducción), su concatenación también será divisible por 3, ya que simplemente se
están sumando los números representados en binario.
Por lo tanto, hemos demostrado que todas las cadenas binarias generadas por la gramática pueden
dividirse entre 3.
b) ¿La gramática genera todas las cadenas binarias con valores divisibles entre 3?
La gramática genera todas las cadenas binarias con valores divisibles entre 3. Esto se debe a que
cada producción de la gramática garantiza que la cadena generada sea divisible por 3. Por lo tanto,
cualquier cadena binaria que sea divisible por 3 se puede generar utilizando las reglas de producción
de la gramática.
2.3.3 Construya un esquema de traducción orientado a la sintaxis (ETOS), que traduzca enteros a
números romanos.
Paso 1: Definir la gramática de la entrada:
La entrada en este caso serán enteros en base 10, por lo que podemos definir una gramática simple:
E -> D | D E
D -> 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
Donde E es una secuencia de dígitos y D representa un dígito.
Paso 2: Definir la gramática de la salida:
La salida será números romanos. Los números romanos se forman con las letras I, V, X, L, C, D y M.
Necesitaremos reglas para traducir los dígitos base 10 a sus equivalentes en números romanos:
R -> ε | I | II | III | IV | V | VI | VII | VIII | IX | X | ... | C | CC | ... | CM
Donde R representa un número romano.
Paso 3: Asociar reglas de traducción:
Ahora necesitamos asociar cada dígito en la entrada con su equivalente en números romanos.
Podemos hacer esto mediante reglas de traducción:
E -> D { traducción(D) }
Donde traducción(D) representa la traducción del dígito D en números romanos.
Por ejemplo:
1 se traduce a "I"
5 se traduce a "V"
10 se traduce a "X"
50 se traduce a "L"
100 se traduce a "C"
500 se traduce a "D"
1000 se traduce a "M"
Paso 4: Definir acciones semánticas:
Las acciones semánticas se utilizan para realizar la traducción real. En este caso, simplemente
construimos la cadena de salida al traducir cada dígito en la entrada a su equivalente en números
romanos.
Por ejemplo:
Si el dígito es 3, la traducción sería "III".
Si el dígito es 7, la traducción sería "VII".
Paso 5: Construir el ETOS:
Con todas las piezas definidas, podemos construir el ETOS. Aquí está el esquema de traducción
orientado a la sintaxis completo:
E -> D { traducción(D) }
D -> 0 {" "} | 1 { "I" } | 2 { "II" } | 3 { "III" } | 4 { "IV" } | 5 { "V" } | 6 { "VI" } | 7 { "VII" } | 8 { "VIII" } | 9 { "IX" }
Este ETOS tomará un entero en base 10 como entrada y producirá su equivalente en números
romanos como salida. Cada dígito se traduce individualmente y se concatenan para formar el número
romano completo.
3.3.5 Escriba definiciones regulares para los siguientes lenguajes:
a) Todas las cadenas de letras en minúsculas que contengan las cinco vocales en orden.
Este lenguaje consiste en cadenas que contienen las cinco vocales (a, e, i, o, u) en orden específico.
Explicación paso a paso:
• Definimos un patrón que coincida con cualquier cantidad de letras minúsculas seguidas,
seguidas de las cinco vocales en el orden especificado.
• Usamos los símbolos de concatenación y repetición de expresiones regulares para lograr esto.
R = [a-z]*a[a-z]*e[a-z]*i[a-z]*o[a-z]*u[a-z]*
b) Todas las cadenas de letras en minúsculas, en las que las letras se encuentren en orden
lexicográfico ascendente.
Explicación paso a paso:
• Definimos un patrón que coincida con cualquier cantidad de letras minúsculas seguidas, en las
que cada letra siguiente sea mayor o igual a la anterior.
• Usamos el operador de rango en las expresiones regulares para definir este orden.
R = [a-z]*a[b-z]*b[c-z]*c[d-z]*d[e-z]*e[f-z]*f[g-z]*g[h-z]*h[i-z]*i[j-z]*j[k-z]*k[l-z]*l[m-z]*m[n-z]*n[o-z]*o[p-
z]*p[q-z]*q[r-z]*r[s-z]*s[t-z]*t[u-z]*u[v-z]*v[w-z]*w[x-z]*x[y-z]*y[z]*
c) Comentarios, que consistan de una cadena rodeada por /* y */, sin un */ entre ellos, a menos que
se encierre entre dobles comillas (").
Explicación paso a paso:
• Definimos un patrón que coincida con "/" seguido de cualquier cantidad de caracteres que no
sean "/", y finalmente seguido por "*/".
• Usamos el operador de negación en las expresiones regulares para garantizar que no haya "/"
entre "/" y "*/".
R = \/\*(?:(?!\*\/).)*\*\/
d) Todas las cadenas de dígitos sin dígitos repetidos. Sugerencia: Pruebe este problema primero con
unos cuantos dígitos, como {0, 1, 2}.
Explicación paso a paso:
• Definimos un patrón que coincida con cualquier cadena de dígitos en la que no se repita ningún
dígito.
• Usamos grupos de captura y referencias inversas para garantizar que no se repita ningún dígito.
R = \b(?:([0-9])(?!.*\1))+\b
h) Todas las cadenas de as y bs que no contengan la subcadena abb.
Explicación paso a paso:
• Definimos un patrón que coincida con cualquier cantidad de "a" seguida opcionalmente de "b",
excepto cuando se forma la subcadena "abb".
• Usamos el operador de negación en las expresiones regulares para evitar la subcadena "abb".
R = ^(?!.*abb)[ab]*$
3.4.9 Las cadenas de Fibonacci se definen de la siguiente manera:
1. s1 = b.
2. s2 = a.
3. 𝑠𝑘 = 𝑠𝑘 −1𝑠𝑘 −2 para k > 2.
Por ejemplo, s3 = ab, s4 = aba y s5 = abaab.
a) ¿Cuál es la longitud de 𝑠𝑛 ?
Para calcular la longitud de 𝑠𝑛 , podemos seguir la definición de las cadenas de Fibonacci. Dado
que 𝑠𝑘 se forma concatenando 𝑠𝑘 − 1 y 𝑠𝑘 − 2, la longitud de 𝑠𝑛 será la suma de las longitudes de
𝑠𝑛 − 1 y 𝑠𝑛 − 2.
Usando esta lógica, podemos calcular la longitud de 𝑠𝑛 recursivamente para n ≥ 3:
𝑠1 = b tiene longitud 1.
𝑠2 = a tiene longitud 1.
Para n > 2, la longitud de 𝑠𝑛 es la suma de las longitudes de 𝑠𝑛 − 1 y 𝑠𝑛 − 2.
Por lo tanto, la longitud de 𝑠𝑛 se puede calcular utilizando la fórmula de Fibonacci.
Desarrollo:
Para 𝑛 = 1, 𝑠1 = 𝑏 tiene longitud 1
Para 𝑛 = 2, 𝑠2 = 𝑎 tiene longitud 1
Para 𝑛 = 3, 𝑠3 = 𝑠2 𝑠1 = 𝑎𝑏. La longitud de 𝑠3 es la suma de las longitudes de 𝑠2 𝑦 𝑠1, es decir, 1 +
1 = 2.
Para 𝑛 = 4, 𝑠3 = 𝑠3 𝑠2 = 𝑎𝑏𝑎. La longitud de 𝑠4 es la suma de las longitudes de 𝑠3 𝑦 𝑠2 , es decir, 2 +
1 = 3.
Y así sucesivamente.
b) Construya la función de fallo para s6.
Para construir la función de fallo para la cadena s6 que es “abaab” aplicaremos el algoritmo KMP
para encontrar la longitud del sufijo más largo que es también un prefijo en cada posición de la
cadena.
Aquí está el proceso paso a paso:
• Inicializamos un arreglo de tamaño igual a la longitud de la cadena, donde cada elemento
inicialmente es cero.
• Recorremos la cadena desde la segunda posición hasta el final.
• En cada posición j, buscamos el sufijo más largo que también es un prefijo de la subcadena
que va desde el inicio hasta j.
• Asignamos la longitud de este sufijo como el valor de la función de fallo en la posición j.
Para la cadena "abaab":
• En la posición 0, la subcadena es "" y no hay ningún sufijo que también sea un prefijo, por
lo que 𝑓 (0) = 0.
• En la posición 1, la subcadena es "a" y no hay sufijos que también sean prefijos, por lo que
𝑓 (1) = 0.
• En la posición 2, la subcadena es "ab" y el sufijo más largo que también es un prefijo es "",
por lo que 𝑓(2) = 0.
• En la posición 3, la subcadena es "aba". El sufijo más largo que también es un prefijo es "a",
por lo que 𝑓(3) = 1.
• En la posición 4, la subcadena es "abaa". El sufijo más largo que también es un prefijo es
"", por lo que 𝑓 (4) = 0.
Entonces, la función de fallo para la cadena "abaab" sería:
𝑓 = [0,0,0,1,0].
Esto indica que en la posición 3, el sufijo más largo que es también un prefijo tiene longitud 1.
En las demás posiciones, el sufijo más largo que es también un prefijo tiene longitud 0.
c) Muestre que la función de fallo para cualquier sn puede expresarse mediante f (1) = f (2) = 0, y
que para 2 < j ≤ |𝑠𝑛 |, f (j) es j − |𝑠𝑘−1 |, en donde k es el entero más largo, de forma que |𝑠𝑘 | ≤ j
+1.
De acuerdo con la definición de la función de fallo, 𝑓(1) y 𝑓(2) siempre son 0. Esto se debe a que
en las posiciones 1 y 2 de cualquier cadena, no puede haber sufijos que también sean prefijos
(excepto para la cadena vacía).
Cálculo de 𝒇(𝒋) para 𝟐 < 𝒋 ≤ |𝑺𝒏 |:
Para calcular 𝑓 (𝑗) para 2 < 𝑗 ≤ |𝑆𝑛 | necesitamos encontrar el sufijo más largo que también es un
prefijo para la subcadena 𝑠𝑖:𝑗 . Esto implica encontrar el entero k más largo tal que |𝑠𝑘 | ≤ j+1. Una
vez que encontramos este k, 𝑓(𝑗) se define como 𝑗 − |𝑠𝑘−1 |. Por lo tanto, podemos demostrar que
para 2 < 𝑗 ≤ |𝑆𝑛 |, 𝑓 (𝑗) = 𝑗 − |𝑆𝑘−1 |, donde k es el entero más largo tal que |𝑠𝑘 | ≤ 𝑗 + 1.
Esta demostración confirma que la función de fallo para cualquier 𝑆𝑛 puede expresarse mediante
𝑓 (1) = 𝑓 (2) = 0 y para 2 < 𝑗 ≤ |𝑆𝑛 |, 𝑓(𝑗) es 𝑗 − |𝑠𝑘−1 | donde k es el entero más largo tal que |𝑠𝑘 | ≤
𝑗 + 1.

3.7.3 Convierta las siguientes expresiones regulares en autómatas finitos deterministas, mediante los
algoritmos 3.23 y 3.20:
Para convertir las expresiones regulares dadas en autómatas finitos deterministas (AFD), primero
utilizaremos el Algoritmo 3.23 (McNaughton-Yamada-Thompson) para construir un autómata finito no
determinista (AFN) a partir de la expresión regular. Luego, utilizaremos el Algoritmo 3.20 para convertir
el AFN en un AFD.
a) (a|b)*
Paso 1: McNaughton-Yamada-Thompson (Algoritmo 3.23)
Para la expresión regular (a∣b)*, el algoritmo McNaughton-Yamada-Thompson genera el siguiente
autómata finito no determinista:
1. Para la expresión regular a, creamos el siguiente AFN:
Estado inicial -> Estado final con transición 'a'
2. Para la expresión regular b
Estado inicial -> Estado final con transición 'b'
3. Para la unión de los dos AFN anteriores, creamos un nuevo estado inicial conectado con
transiciones épsilon a los estados iniciales de los AFN correspondientes, y creamos un nuevo
estado final con transiciones épsilon desde los estados finales de los AFN correspondientes.
4. Finalmente, para el cierre de Kleene ∗, agregamos transiciones épsilon desde los estados
finales a los estados iniciales.
El AFN resultante es:
Estado actual Símbolo de entrada Estado siguiente
𝑞0 𝜀 𝑞1 , 𝑞2
𝑞0 a -
𝑞0 b -
𝑞1 𝜀 -
𝑞1 a 𝑞2
𝑞1 b -
𝑞2 𝜀 𝑞0 , 𝑞1
𝑞2 a -
𝑞2 b 𝑞1

𝑞1

𝑎
𝜀

𝜀, 𝑏
𝑞0
𝜀 𝑞2
𝜀
Paso 2: Construcción de subconjuntos (Algoritmo 3.20)
Utilizaremos el algoritmo de construcción de subconjuntos para convertir el AFN en un AFD:
1. Comenzamos con el conjunto de cierre-épsilon del estado inicial del AFN como el estado inicial
del AFD.
2. Para cada conjunto de estados en el AFD, encontramos los estados a los que podemos llegar
con cada símbolo del alfabeto.
3. Los conjuntos resultantes se convierten en estados del AFD.
4. El AFD resultante es un AFD completo, donde si no hay una transición definida para un símbolo
desde un estado dado, se agrega un estado de "error".
Estado actual Símbolo de entrada Estados siguientes
𝑞0 𝜀 𝑞1 , 𝑞2
𝑞1 a 𝑞2
𝑞2 b 𝑞1
𝑞2 𝜀 𝑞0 , 𝑞1

𝑞1

𝜀 𝜀, 𝑎
𝑏
𝜀
𝑞0 𝑞2
𝜀

3.8.3 Suponga que vamos a revisar la definición de un AFD para permitir cero o una transición saliente
de cada estado, en cada símbolo de entrada (en vez de que sea exactamente una transición, como en
la definición estándar del AFD). De esta forma, algunas expresiones regulares tendrían “AFDs” más
pequeños en comparación con la definición estándar de un AFD. Proporcione un ejemplo de una
expresión regular así.
• Bajo la definición revisada del AFD, en cada estado del autómata, se permitiría cero o una
transición saliente para cada símbolo de entrada. Esto significa que cada estado puede tener
transiciones para los símbolos 'a' y 'b', pero no necesariamente debe tener una transición para
cada uno de ellos.
• Por lo tanto, el "AFD" resultante tendría solo un estado, el estado inicial, y desde este estado
podría haber cero o una transición para los símbolos 'a' y 'b'. No habría necesidad de estados
adicionales para manejar las transiciones adicionales.
• En resumen, la expresión regular (𝑎|𝑏) ∗ daría lugar a un "AFD" más pequeño bajo la definición
revisada, ya que solo necesitaría un estado para representar todas las posibles transiciones.
3.9.4 Construya los AFDs con el mínimo número de estados para las siguientes expresiones regulares:
a) (a|b)*a(a|b)
Para construir un autómata finito determinista (AFD) con el mínimo número de estados para la
expresión regular (a∣b) ∗ a(a∣b), primero necesitamos construir un autómata finito no determinista
(AFN) utilizando el algoritmo de McNaughton-Yamada-Thompson y luego aplicar el algoritmo de
construcción de subconjuntos para convertir el AFN en un AFD.
Estado actual Símbolo de entrada Estados siguientes
𝑞0 𝜀 𝑞1 , 𝑞2
𝑞1 𝜀 𝑞2 , 𝑞5
𝑞2 𝑎 𝑞3
𝑞3 𝜀 𝑞4
𝑞4 𝜀 𝑞1 , 𝑞5
𝑞5 𝑏 𝑞6
𝑞6 𝜀 𝑞1 , 𝑞5
𝜀

𝜀 𝑏
𝑞1 𝑞5 𝑞6
𝜀
𝜀
𝜀 𝜀

𝑞0 𝜀 𝑞4

𝜀 𝜀
𝑎
𝑞2 𝑞3

Paso 2: Construcción del AFD con el algoritmo de construcción de subconjuntos:

Para el AFD, utilizaremos la tabla de transiciones que derivamos previamente:

Estado actual a b
{} 𝑞1 {}
𝑞1 𝑞2 𝑞5
𝑞2 𝑞3 {}
𝑞3 𝑞4 {}
𝑞4∗ 𝑞1 𝑞5
𝑞5 𝑞6 {}
𝑞6∗ 𝑞1 𝑞5

También podría gustarte