• No se han encontrado resultados

Algoritmo de análisis

Gramáticas de precedencia simple

4.4.7. Algoritmo de análisis

1. Se almacenan las producciones de la gramática en una tabla.

2. Se construye la matriz de precedencia MP de dimensiones N×N (N = cardinal o número de elementos de Σ), tal que

MP(i,j) = 0 si no existe relación entre Uiy Uj

= 1 si Ui<. Uj

= 2 si Ui=. Uj

= 3 si Ui>. Uj

3. Se inicializa una pila con el símbolo



y se añade el símbolo



al final de la cadena de entrada.

4. Se compara el símbolo situado en la cima de la pila con el siguiente símbolo de entrada. 5. Si no existe ninguna relación, error sintáctico: cadena rechazada (fin del algoritmo). 6. Si existe la relación <. o la relación =., se introduce el símbolo de entrada en la pila y

se elimina de la entrada. Volver al paso 4.

7. Si la relación es >., el asidero termina en la cima de la pila.

8. Se recupera el asidero de la pila, sacando símbolos hasta que el símbolo en la cima de la pila esté en relación <. con el último sacado.

9. Se compara el asidero con las partes derechas de las reglas.

10. Si no coincide con ninguna, error sintáctico: cadena rechazada (fin del algoritmo). 11. Si coincide con una, se coloca la parte izquierda de la regla en el extremo izquierdo de

la cadena que queda por analizar.

12. Si en la pila sólo queda



y la cadena de entrada ha quedado reducida al axioma segui- do del símbolo



, la cadena ha sido reconocida (fin del algoritmo). En caso contrario, volver al paso 4.

Sea la gramática G = ({a,b,c}, {S}, S, {S ::= aSb | c}). Las matrices de las relacio- nes son: =. : 0 0 1 0 F : 0 1 0 1 L : 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 F+ = F L+ = L F* : 1 1 0 1 0 1 0 0 0 0 1 0 0 0 0 1

Aplicando las expresiones de la Sección 4.4.3, se obtiene: <. : 0 0 0 0 >. : 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 Ejemplo 4.13

Con lo que la matriz de precedencia queda: S a b c



S =. >. a =. <. <. >. b >. >. c >. >.



<. <. <. <.

Se analizará ahora la cadena aacbb:

Pila Relación Entrada Asidero Regla a aplicar



<. aacbb





<.a <. acbb





<.a<.a <. cbb





<.a<.a.<.c >. bb



c S::=c



<.a<.a =. Sbb





<.a<.a=.S =. bb





<.a<.a=.S=.b >. b



aSb S::=aSb



<.a =. Sb





<.a=.S =. b





<.a=.S=.b >.



aSb S::=aSb



<. S



Pila Relación Entrada Asidero Regla a aplicar



<. aabb





<.a <. abb





<.a<.a No hay bb



Luego la cadena es aceptada. Se analizará ahora la cadena aabb:

Se analizará ahora la cadena acbb:

Pila Relación Entrada Asidero Regla a aplicar



<. acbb





<.a <. cbb





<.a<.c >. bb



c S::=c



<.a =. Sbb





<.a=.S =. bb





<.a=.S=.b >. b



aSb S::=aSb



<. Sb





<.S =. b





<.S=.b >.



Sb No hay

Luego la cadena es rechazada.

4.4.8.

Funciones de precedencia

La matriz de precedencias ocupa N×N posiciones de memoria. A veces es posible construir dos funciones de precedencia que ocupen sólo 2×N. Esta operación se llama linealización de la ma- triz. Dichas funciones, de existir, no son únicas (hay infinitas).

Para el ejemplo anterior valen las dos funciones siguientes:



S a b c



f 0 2 2 3 3

g 2 3 2 3 0

Si es posible construir f y g, se verifica que • f(U)=g(V) ⇒ U =. V

• f(U)<g(V) ⇒ U <. V • f(U)>g(V) ⇒ U >. V

Existen matrices que no se pueden linealizar. Ejemplo: A B

A = > B = =

En este caso, debería cumplirse que: f(A) = g(A) f(A) > g(B) f(B) = g(A) f(B) = g(B) Es decir:

f(A) > g(B) = f(B) = g(A) = f(A)⇒ f(A) > f(A)

Con lo que se llega a una contradicción. Cuando no hay inconsistencias, el siguiente algorit- mo construye las funciones de precedencia:

• Se dibuja un grafo dirigido con 2×N nodos, llamados f1, f2,…, fN, g1, g2,…, gN,

con un arco de fia gjsi Ui>.= Ujy un arco de gja fisi Ui<.= Uj.

• A cada nodo se le asigna un número igual al número total de nodos accesibles desde él (in- cluido él mismo). El número asignado a fise toma como valor de f(Ui)y el asignado a

gies g(Ui).

Demostración:

• Si Ui=. Uj, hay una rama de fia gjy viceversa, luego cualquier nodo accesible desde fi

es accesible desde gjy viceversa. Luego f(Ui)=g(Uj).

• Si Ui>. Uj, hay una rama de fia gj. Luego cualquier nodo accesible desde gjes accesi-

ble desde fi. Luego f(Ui)>=g(Uj).

Si f(Ui)=g(Uj), existe un camino cerrado fi→gj→fk→gl→…→gm→fi, lo que implica

que Ui >. Uj, Uk <.= Uj, Uk >.= Ul, … Ui <.= Um y reordenando:

Ui>.Uj>.=Uk>.=Ul>.=…>.=Um>.=Ui, es decir: f(Ui)>f(Ui), con lo que se llega a

una contradicción. Luego el caso de igualdad de valores de las funciones queda excluido y se deduce que f(Ui)>g(Uj).

• Si Ui<. Uj, la demostración es equivalente.

La Figura 4.64 describe la aplicación de este algoritmo al ejemplo anterior, y explica cómo se obtuvieron las funciones mencionadas al principio de esta sección.

g(S) g(a) g(b) g(c)

f(S) f(a) f(b) f(c)

El algoritmo descrito puede mecanizarse. El grafo descrito equivale a la relación existe un

arco del nodo x al nodo y. Dicha relación posee su matriz Booleana correspondiente, de dimen-

siones 2N×2N, que llamaremos B, y que puede representarse así: (0) (>.=)

(<.=)’ (0)

A partir de B, construimos B*. Entonces se verifica que f(Ui)es igual al número de unos en

la fila i, mientras g(Ui)es igual al número de unos en la fila N+i.

En el ejemplo anterior, B resulta ser la matriz: 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0

A partir de B, obtenemos B*, que resulta ser la matriz: 1 0 0 0 0 0 1 0 0 1 0 0 1 0 0 0 1 0 1 0 0 0 1 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 0 0 1 0 0 1 1 0 0 1 0 0 0 0 0 1 0 0 1 0 0 1 0 0 1

Con lo que las dos funciones f y g resultan ser las indicadas al principio de esta sección. Para completarlas, basta añadir los símbolos de principio y fin de cadena, a los que se asigna el va- lor 0. A continuación se muestran de nuevo los resultados obtenidos. Obsérvese que, si se suma cualquier número entero a todos los valores de f y g, se obtienen dos nuevas funciones que tam- bién cumplen todas las condiciones. Por eso, si existe un par de funciones f y g, tendremos in- finitas, todas equivalentes.



S a b c



f 0 2 2 3 3

g 2 3 2 3 0

El uso de las funciones supone cierta pérdida de información respecto al uso de la matriz, pues desaparecen los lugares vacíos en la tabla de precedencias, que conducían directamente a la de- tección de algunos casos de error. Sin embargo, estos casos serán detectados más tarde, de otra manera. Para comprobarlo, analicemos por medio de las funciones la cadena aabb y veremos que en este caso basta con un paso más para detectar la condición de error.

Resumen

En este capítulo se describen algunos de los métodos que suelen utilizarse para construir los ana- lizadores sintácticos de los lenguajes independientes del contexto. En una primera sección del ca- pítulo se describen los conjuntos primero y siguiente asociados a una gramática, pues son necesarios para describir los métodos que aparecen en el resto del capítulo. Estos métodos de aná- lisis pueden clasificarse en dos categorías: análisis descendente y análisis ascendente. Dentro del análisis descendente se describe, en primer lugar, el análisis descendente con vuelta atrás cuya ineficiencia se soluciona con las gramáticas LL(1) y el método de análisis descendente selectivo. Las operaciones fundamentales de los algoritmos de análisis ascendente son el desplazamien- to de los símbolos de entrada necesarios para reconocer los asideros y la reducción de los mis- mos. Estas técnicas se basan en la implementación del autómata a pila para la gramática independiente del contexto del lenguaje considerado y éste, a su vez, en el autómata finito que reconoce los asideros del análisis. Hay diferentes maneras de construir este autómata finito. Los analizadores estudiados en orden creciente de potencia, son LR(0), SLR(1), LR(1) y LALR(1). Su principal diferencia consiste en que, para reducir una regla, comprueban condiciones más es- trictas respecto a los próximos símbolos que el análisis encontrará en la entrada: SLR(1) tiene en cuenta el símbolo siguiente y LR(1) y LALR(1) consideran de forma explícita un símbolo de ade- lanto. Los algoritmos LR y LALR se podrían generalizar para valores de k>1 pero la compleji- dad que implica gestionar más de un símbolo de adelanto desaconseja su uso.

Otro método de análisis ascendente, que no se utiliza mucho en compiladores completos, pero sí en analizadores de expresiones, hojas de cálculo, bases de datos, etc., utiliza gramáticas de pre- cedencia simple. El método se basa en tres relaciones, llamadas de precedencia, que permiten lo- calizar los asideros del análisis con un algoritmo muy sencillo y eficiente. Estas relaciones pueden construirse fácilmente, por simple observación de las reglas de producción, o de forma automática, mediante operaciones realizadas sobre matrices booleanas. Por último, es posible mejorar el algoritmo sustituyendo las matrices por dos funciones de precedencia.

Ejercicios

1. Considérese el lenguaje de los átomos en lenguaje Prolog. Todos ellos tienen un identifica-

Documento similar