21.06.2016 Views

Análisis Sintáctico

Create successful ePaper yourself

Turn your PDF publications into a flip-book with our unique Google optimized e-Paper software.

1


INSTITUTO TECNOLÓGICO DE FELIPE<br />

CARRILLO PUERTO<br />

Ingeniería en sistemas computacionales<br />

Semestre VI<br />

Aula J1<br />

Grupo A<br />

Unidad 6<br />

(<strong>Análisis</strong> <strong>Sintáctico</strong>)<br />

LENGUAJES Y AUTOMATAS 1<br />

NOMBRE DEL ALUMNO: Erwin Alexander Villegas tun<br />

Ezer Abisai Ayala Mutul<br />

Wilberth Abigael Balam Yam<br />

NOMBRE DEL PROFESOR (A): ISC. Paloma Góngora Sabido<br />

Felipe Carrillo Puerto, Quintana Roo a 20 de Junio del 2016<br />

2


INDICE<br />

Contenido<br />

Introducción ............................................................................................................................... 4<br />

Gramáticas libre de contexto .................................................................................................. 5<br />

BNF .............................................................................................................................................. 8<br />

Árbol de derivación ................................................................................................................ 11<br />

Propiedades de un árbol de derivación. ............................................................................. 12<br />

Formas Normales de Chomsky ............................................................................................ 15<br />

Variables accesibles ................................................................................................................. 15<br />

Variables generativas ......................................................................................................... 15<br />

Variables útiles .................................................................................................................... 16<br />

Diagramas de sintaxis ............................................................................................................ 18<br />

Descripción .......................................................................................................................... 18<br />

Como se constituyen .......................................................................................................... 19<br />

Características ..................................................................................................................... 20<br />

Ambigüedad ............................................................................................................................. 22<br />

Eliminación de Ambigüedad ............................................................................................. 23<br />

Tipos de Ambigüedad ........................................................................................................ 23<br />

Generación de matriz predictiva (FIRS y FOLLOW) ......................................................... 25<br />

Construcción de la Tabla ................................................................................................... 27<br />

Tipos de analizadores ............................................................................................................ 34<br />

Algoritmo .............................................................................................................................. 35<br />

Manejo de errores ................................................................................................................... 41<br />

Estrategias para recuperarse de los errores ..................................................................... 43<br />

Generadores de analizadores sintácticos .......................................................................... 46<br />

Generador sintáctico GNU BISON ....................................................................................... 47<br />

Conclusión ............................................................................................................................... 48<br />

Bibliografías ............................................................................................................................. 49<br />

3


Introducción<br />

Aquí encontrara temas relacionados con el análisis sintáctico, como primer tema<br />

encontrará la definición formal de GLC (Gramáticas Libres de Contexto), así como<br />

la definición de Árbol de derivación su representación gráfica, propiedades de un<br />

árbol de derivación y sus respectivos ejemplos. Otro tema importante son las formas<br />

normales de Chomsky nos menciona que si una gramática formal esta en forma<br />

normal de Chomsky si todas sus reglas de producción son de algunas de las<br />

siguientes formas ABC o Aa entre otras que se muestran en el documento. Los<br />

diagramas de sintaxis también conocidas como diagramas de ferrocarril, son la<br />

forma de representar una gramática libre de contexto, para la mayoría de las<br />

personas se les hace más comprensible representarlos en diagramas, de igual<br />

forma podrá apreciar varios ejemplos relacionados con este tema. Una gramática<br />

libre de contexto es ambigua si existe cierta cadena que tiene más de una derivación<br />

por la izquierda o más de una derivación por la derecha o si tiene dos o más árboles<br />

de derivación; existe dos tipos de ambigüedad, la Inherentes y ambigua; los<br />

ejemplos mostrados son sobre la eliminación de ambigüedad. El siguiente tema<br />

trata sobre la generación de matriz predictiva aquí se utiliza first y follow para la<br />

generación de estas matrices con los ejemplos que se mostraran se explicara más<br />

a detalle cómo se utiliza estos dos tipos de cálculos. Hay dos tipos de analizadores<br />

sintácticos, los descendentes que se construye el árbol de análisis sintético<br />

partiendo del símbolo inicial aplicando las producciones mediante derivaciones por<br />

la izquierda; y las Ascendentes estas construye el árbol de análisis sintético<br />

partiendo de la frase a reconocer y aplicando las producciones mediantes<br />

reducciones hasta llegar al símbolo inicial de la gramática. Un compilador es un<br />

sistema que en la mayoría de los casos maneja una entrada incorrecta, sobre todo<br />

en las primeras etapas de la creación de un programa. Uno de los errores más<br />

complicados que se pueden encontrar, es el manejo de errores de sintaxis y como<br />

objetivo tiene que indicar los errores e forma clara y precisa, aclarar el tipo de error<br />

y su localización también debe de recuperarse del error para poder seguir<br />

examinando la entrada. Como último tema tenemos los generadores de<br />

analizadores sintácticos, se agregaron varios programas de generadores entre ellos<br />

4


Gramáticas libre de contexto<br />

Estas gramáticas, conocidas también como gramáticas de tipo 2 o gramáticas<br />

independientes del contexto, son las que generan los lenguajes libres o<br />

independientes del contexto. Los lenguajes libres del contexto son aquellos que<br />

pueden ser reconocidos por un autómata de pila determinístico o no determinístico.<br />

Estas gramáticas producen los lenguajes Libres de Contexto (abreviado “LLC”)<br />

<br />

<br />

<br />

Capturan la noción de constituyente sintáctico y la noción de orden.<br />

Herramienta formal que puede ser vista tanto desde un punto de vista<br />

generador como estructurador.<br />

Propiedades computacionales interesantes: se puede reconocer en tiempo<br />

polinómico.<br />

Como toda gramática se definen mediante una cuádrupla G = (N, T, P, S), siendo<br />

‣ N es un conjunto finito de símbolos no terminales<br />

‣ T es un conjunto finito de símbolos terminales<br />

‣ P es un conjunto finito de producciones<br />

‣ S es el símbolo distinguido o axioma S ∉ (N ∪ T)<br />

N ∩ T = ∅<br />

En una gramática libre del contexto, cada producción de P tiene la forma<br />

A ∈ N ∪ {S}<br />

A → ω<br />

ω ∈ (N ∪ T) * - {ε}<br />

Es decir, que en el lado izquierdo de una producción pueden aparecer el símbolo<br />

distinguido o un símbolo no terminal y en el lado derecho de una producción<br />

cualquier cadena de símbolos terminales y/o no terminales de longitud mayor o igual<br />

que 1. La gramática puede contener también la producción S → ε si el<br />

lenguaje que se quiere generar contiene la cadena vacía.<br />

5


1. Los terminales son los símbolos básicos a partir de los cuales se forman las<br />

cadenas. El término “nombre de token” es un sinónimo de “terminal”; con<br />

frecuencia usaremos la palabra “token” en vez de terminal, cuando esté claro<br />

que estamos hablando sólo sobre el nombre del token. Asumimos que las<br />

terminales son los primeros componentes de los tokens que produce el<br />

analizador léxico. Los terminales son las palabras reservadas if y else, y los<br />

símbolos “(” y “)”.<br />

2.<br />

Los no terminales son variables sintácticas que denotan conjuntos de<br />

cadenas. En (4.4), instr y expr son no terminales. Los conjuntos de cadenas<br />

denotados por los no terminales ayudan a definir el lenguaje generado por la<br />

gramática. Los no terminales imponen una estructura jerárquica sobre el<br />

lenguaje, que representa la clave para el análisis sintáctico y la traducción.<br />

3.<br />

En una gramática, un no terminal se distingue como el símbolo inicial, y el<br />

conjunto de cadenas que denota es el lenguaje generado por la gramática.<br />

Por convención, las producciones para el símbolo inicial se listan primero.<br />

4.<br />

Las producciones de una gramática especifican la forma en que pueden<br />

combinarse los terminales y los no terminales para formar cadenas. Cada<br />

producción consiste en:<br />

a) Un no terminal, conocido como encabezado o lado izquierdo de la<br />

producción; esta producción define algunas de las cadenas denotadas por el<br />

encabezado.<br />

b) El símbolo →. Algunas veces se ha utilizado ::= en vez de la flecha.<br />

c) Un cuerpo o lado derecho, que consiste en cero o más terminales y no<br />

terminales.<br />

d) Los componentes del cuerpo describen una forma en que pueden construirse<br />

las cadenas del no terminal en el encabezado.<br />

6


Ejemplo 1:<br />

La siguiente gramática genera las cadenas del lenguaje L 1 = {wcw R / w ∈ {a, b}<br />

* }<br />

G 1 = ({A}, {a, b, c}, P 1 , S 1 ), y P 1 contiene las siguientes producciones<br />

S 1 → A<br />

A<br />

A<br />

A<br />

→ aAa<br />

→ bAb<br />

→ c<br />

Ejemplo 2:<br />

La siguiente gramática genera las cadenas del lenguaje L 2 = {0 i 1 i+k 2 k 3 n+1<br />

/ i, k, n ≥ 0 }<br />

Casos Cadenas de L 2<br />

si n, i, k > 0 0 i 1 i+k 2 k 3 n+1<br />

si n=0 y i, k > 0 0 i 1 i+k 2 k 3<br />

si i=0 y n, k >0 1 k 2 k 3 n+1<br />

si k=0 y n, i >0 0 i 1 i 3 n+1<br />

si n, i=0 y k >0 1 k 2 k 3<br />

si n, k=0 y i >0 0 i 1 i 3<br />

si i, k =0 y n >0 3 n+1<br />

si n,i,k=0 3<br />

G 2 = ({A, B, C}, {0, 1, 2, 3}, P 2 , S 2 ), y P 2 contiene las producciones<br />

7


S 2 → ABC B → 1B2<br />

S 2 → AC B → 12<br />

S 2 → BC C → 3C<br />

S 2 → C C → 3<br />

A<br />

→ 0A1<br />

A → 01<br />

BNF<br />

Las gramáticas libres del contexto se escriben, frecuentemente, utilizando una<br />

notación conocida como BNF (Backus-Naur Form). BNF es la técnica más<br />

común para definir la sintaxis de los lenguajes de programación.<br />

En esta notación se deben seguir las siguientes convenciones:<br />

‣ los no terminales se escriben entre paréntesis angulares < ><br />

‣ los terminales se representan con cadenas de caracteres sin paréntesis<br />

angulares<br />

‣ el lado izquierdo de cada regla debe tener únicamente un no terminal<br />

(ya que es una gramática libre del contexto)<br />

‣ el símbolo ::=, que se lee “se define como” o “se reescribe como”, se utiliza<br />

en lugar de →<br />

‣ varias producciones del tipo<br />

::= <br />

8


::= <br />

.<br />

.<br />

.<br />

::= <br />

se pueden escribir como ::= ... <br />

Ejemplo 3:<br />

La siguiente es una definición BNF del lenguaje que consiste de cadenas de<br />

paréntesis anidados:<br />

:: = <br />

::= ( ) ( )<br />

Por ejemplo las cadenas ( ) ( ( ) ) y ( ) ( ) ( ) son cadenas válidas. En cambio las<br />

cadenas ( ( ) y ( ) ) ) no pertenecen al lenguaje.<br />

9


10


Árbol de derivación<br />

Un árbol de análisis sintáctico es una representación gráfica de una derivación que<br />

filtra el orden en el que se aplican las producciones para sustituir los no terminales.<br />

Cada nodo interior de un árbol de análisis sintáctico representa la aplicación de una<br />

producción.<br />

Es una representación gráfica (en forma de árbol invertido) de un proceso de<br />

derivación en una gramática. Se define el árbol de derivación como sigue:<br />

<br />

<br />

<br />

<br />

la raíz del árbol será el símbolo inicial de la gramática<br />

los nodo interiores del árbol están etiquetados por los símbolos no terminales<br />

las hojas están etiquetadas por símbolos terminales<br />

si un nodo interior etiquetado por A, posee como hijos los nodos etiquetados<br />

por X1,X2, …Xn , entonces A→ X1,X2, …Xn es una producción de la<br />

gramática, en donde Xi , representa símbolo terminal o no terminal.<br />

Sea la siguiente gramática:<br />

G=( Σ={a, b}, N={S,A,B},S P ) P: S→aABAa , A→ε |aA , B→ε|bB la construcción de<br />

un árbol de derivación en el proceso de la generación de la palabra aa es el<br />

siguiente:<br />

11


Propiedades de un árbol de derivación.<br />

Sea G = (N, T, S, P) una gramática libre de contexto, sea A Є N una variable.<br />

Diremos que un árbol TA= (N, E) etiquetado es un árbol de derivación asociado a G<br />

si verifica las propiedades siguientes:<br />

La raíz del árbol es un símbolo no terminal<br />

Cada hoja corresponde a un símbolo terminal o λ.<br />

Cada nodo interior corresponde a un símbolo no terminal.<br />

Para cada cadena del lenguaje generado por una gramática es posible construir (al<br />

menos) un árbol de derivación, en el cual cada hoja tiene como rótulo uno de los<br />

símbolos de la cadena.<br />

Árbol de derivación.<br />

12


Ejemplo:<br />

Sea G=(N, T, S, P) una GLC con P: S→ ab|aSb<br />

La derivación de la cadena aaabbb será: S → aSb → aaSbb → aaabbb y el árbol<br />

de derivación:<br />

En lo que sigue, realizaremos con frecuencia el análisis sintáctico produciendo una<br />

derivación por la izquierda o por la derecha, ya que hay una relación de uno a uno<br />

entre los árboles de análisis sintáctico y este tipo de derivaciones. Tanto las<br />

derivaciones por la izquierda como las de por la derecha eligen un orden específico<br />

para sustituir símbolos en las formas de las oraciones, por lo que también filtran las<br />

variaciones en orden. No es difícil mostrar que todos los árboles sintácticos tienen<br />

asociadas una derivación única por la izquierda y una derivación única por la<br />

derecha.<br />

13


14


Formas Normales de Chomsky<br />

Una gramática formal está en Forma normal de Chomsky si todas sus reglas de<br />

producción son de alguna de las siguientes formas:<br />

A → BC o<br />

A → a o<br />

donde A, B y C son símbolos no terminales (o variables) y α es un símbolo terminal.<br />

Todo lenguaje independiente del contexto que no posee a la cadena vacía, es<br />

expresable por medio de una gramática en forma normal de Chomsky (GFNCH) y<br />

recíprocamente. Además, dada una gramática independiente del contexto, es<br />

posible algorítmicamente producir una GFNCH equivalente, es decir, que genera el<br />

mismo lenguaje.<br />

Una gramática sin reglas de producción unitarias, sin símbolos inútiles ni anulables<br />

puede expresarse en la FNC, en la cual todas las reglas de producción tienen del<br />

lado derecho un terminal o bien 2 variables, es decir, las reglas de producción son<br />

de la forma<br />

Variables accesibles:<br />

<br />

Si existe una derivación desde el símbolo inicial que contiene X, es decir,<br />

existe $ → * α Xβ donde α, β Є∑*<br />

Variables generativas:<br />

<br />

Si existe una derivación desde el la variable que produce una sentencia, es<br />

decir, existe X →* ω donde ω Є *T.<br />

15


Variables útiles:<br />

Si existe una derivación desde el símbolo inicial usando que produce una sentencia<br />

ω, es decir, existe $ →* α X β →*ω donde α, β Є ∑* y ω Є ∑*T.<br />

Ejemplo, convirtamos la gramática siguiente a la FNC<br />

<br />

A 0A0 | 1A1|<br />

0 | 1<br />

<br />

Solución:<br />

La primera regla de producción<br />

A 0A0 la podemos cambiar por las tres siguientes<br />

<br />

<br />

<br />

A CB<br />

B AC<br />

C 0<br />

La segunda regla de producción A 1A1<br />

la podemos cambiar por las tres<br />

siguientes<br />

<br />

<br />

<br />

A UD<br />

D AU<br />

U 1<br />

Las últimas dos reglas sí cumplen con la FNC, entonces la gramática normalizada<br />

queda:<br />

<br />

<br />

<br />

<br />

<br />

A CB |UD | 0 | 1<br />

B AC<br />

C 0<br />

D AU<br />

U 1<br />

16


17


Diagramas de sintaxis<br />

Los diagramas sintácticos, de sintaxis o diagramas del ferrocarril son una forma de<br />

representar una gramática libre de contexto. Representan una alternativa gráfica<br />

para la Forma de Backus-Naur (BNF, por sus siglas en inglés) o la Forma Extendida<br />

de Backus-Naur (EBNF, por sus siglas en ingles).<br />

Los diagramas de ferrocarril son más comprensibles para la mayoría de la gente.<br />

Alguna parte de la popularidad del formato de intercambio de datos JSON se debe<br />

a su representación en los diagramas de ferrocarril.<br />

Descripción<br />

A grandes rasgos es un grafo dirigido en el que los nodos representan los símbolos<br />

terminales y no terminales de la gramática, y los arcos expresan las secuencias en<br />

que pueden combinarse tales símbolos para formar frases aceptables según la<br />

gramática.<br />

18


Como se constituyen<br />

Cada diagrama de sintaxis representa un símbolo no terminal (que se puede<br />

expandir), de manera que la gramática completa estará formada por tantos<br />

diagramas distintos e interrelacionados como no<br />

terminales se quieran incluir en su descripción.<br />

19


Características<br />

‣ Los símbolos terminales (palabras o tokens) se dibujan como círculos o<br />

elipses etiquetadas con el nombre del token<br />

‣ Los no terminales que aparecen en un grafo se dibujan como rectángulos<br />

etiquetados<br />

con<br />

su nombre correspondiente.<br />

‣ Todo diagrama posee un punto de entrada (generalmente situado a la<br />

izquierda)<br />

y un punto de salida (a la derecha), y que están representados por un arco<br />

sin origen y un arco sin destino respectivamente.<br />

Ejemplos<br />

Diagrama de sintaxis que muestra las diferentes instrucciones que pueden utilizarse<br />

en un algoritmo.<br />

20


Diagrama de sintaxis para la instrucción de asignación de un valor a una variable.<br />

Diagrama de sintaxis para la instrucción sí.<br />

Identificadores<br />

21


Ambigüedad<br />

Una gramática ambigua es aquella que produce más de una derivación por la<br />

izquierda, o más de una derivación por la derecha para el mismo enunciado.<br />

Ejemplo: La gramática de expresiones aritméticas permite dos derivaciones por la<br />

izquierda distintas para el enunciado id + id ∗ id:<br />

Para la mayoría de los analizadores sintácticos, es conveniente que la gramática no<br />

tenga ambigüedades, ya que de lo contrario, no podemos determinar en forma única<br />

qué árbol de análisis sintáctico seleccionar para un enunciado. En otros casos, es<br />

conveniente usar gramáticas ambiguas elegidas con cuidado, junto con reglas para<br />

eliminar la ambigüedad, las cuales “descartan” los árboles sintácticos no deseados,<br />

dejando sólo un árbol para cada enunciado.<br />

22


Eliminación de Ambigüedad<br />

Una GLC es ambigua si existe una cadena w Є L(G) que tiene más de una<br />

derivación por la izquierda o más de una derivación por la derecha o si tiene dos o<br />

más arboles de derivación.<br />

En casi de y que toda cadena w Є L (G) tenga un único árbol de derivación no es<br />

ambigua.<br />

Ejemplo: La gramática S → aS| Sa | a es ambigua porque aa tiene dos derivaciones<br />

por la izquierda S Þ aS Þ aa S Þ Sa Þ aa.<br />

Tipos de Ambigüedad<br />

Dentro del estudio de gramáticas existen dos tipos fundamentales de ambigüedad,<br />

los cuales son:<br />

Ambigüedad<br />

Inherente:<br />

Las gramáticas que presentan este tipo de ambigüedad no pueden utilizarse para<br />

lenguajes de programación, ya que por más transformaciones que se realicen sobre<br />

ellas, nunca se podrá eliminar completamente la ambigüedad que presentan:<br />

Un lenguaje L es inherentemente ambiguo si todas sus gramáticas; si existe cuando<br />

menos una gramática no ambigua para L, L no es ambiguo.<br />

23


El lenguaje de las expresiones no es Ambiguo<br />

Las expresiones regulares no son ambiguas<br />

Ejemplo de un lenguaje inherentemente ambiguo:<br />

La gramática es ambigua: hay cadenas con más de una derivación más izquierda:<br />

24


Generación de matriz predictiva (FIRS y FOLLOW)<br />

FIRST: Sea G:= (V; ∑; Q0; P) una gramática libre de contexto. Para cada forma<br />

sentencial α Є (V U ∑)* y para cada k Є N definiremos la función.<br />

En otras palabras, el operador F IRST k asocia a cada forma sentencial los primeros<br />

k símbolos de cualquier forma terminal alcanzable desde α mediante derivaciones<br />

“masa la izquierda".<br />

FOLLOW: Con las mismas notaciones anteriores, para cada forma sentencial α Є<br />

(V U ∑)* definiremos la función FOLLOWG GK (α) del modo siguiente.<br />

De nuevo nos ocuparemos solamente de FOLLOW: = FOLLOW1. Obsérvese que<br />

FOLLOW k (α) ⊂ ∑* y que para cada x Є FOLLOW (α), Ixl ≤ k. Obsérvese que para<br />

cada variable A Є V, FOLLOW(A) son todos los símbolos terminales que pueden<br />

aparecer a la derecha de A en alguna forma sentencial de la gramática.<br />

25


• Reglas para First Sets<br />

1. Si X es un terminal entonces First(X) es justamente X.<br />

2. Si existe una producción X → ε entonces agregue ε a first(X)<br />

3. Si existe una producción X → Y1Y2..Yk entonces agregue first(Y1Y2..Yk) a<br />

first(X)<br />

4. First(Y1Y2..Yk) es ya sea<br />

5. First(Y1) (si First(Y1) no contiene ε) o (si First(Y1) contiene ε) entonces First<br />

(Y1Y2..Yk) está todo en First(Y1) como también en<br />

First(Y2..Yk)<br />

6. Si First(Y1) First(Y2)..First(Yk) contienen ε entonces agregue ε a<br />

First(Y1Y2..Yk). FIRST(α): devuelve el conjunto de todos los terminales que<br />

se pueden encontrar a la cabeza de cualquier derivación de la frase α.<br />

• Reglas para Follow Sets<br />

1. Primero ingrese $ (el fin de una entrada) en Follow(S) (S es el símbolo de<br />

partida)<br />

2. Si existe una producción A → aBb, (donde a puede ser un string completo)<br />

entonces todo en FIRST(b) excepto para ε está en FOLLOW(B).<br />

3. Si existe una producción A → aB, entonces todo en FOLLOW(A) está en<br />

FOLLOW(B)<br />

4. Si existe una producción A → aBb, donde FIRST(b) contiene ε, entonces todo<br />

en FOLLOW(A) está en FOLLOW(B) FOLLOW(A): devuelve el conjunto de<br />

todos los terminales que se pueden encontrar siguiendo a A en cualquier<br />

derivación posible.<br />

26


Ejemplo.<br />

1) E → TE'<br />

2) E' → +TE'<br />

3) E' → ε<br />

4) T → FT'<br />

5) T' → *FT'<br />

6) T' → ε<br />

7) F → (E)<br />

8) F → id<br />

Construcción de la Tabla<br />

Los analizadores descendentes dirigidos por tabla están constituidos por dos<br />

elementos que se utilizan para llevar a cabo el proceso de análisis sintáctico.<br />

• Una pila, donde se almacenan símbolos gramaticales.<br />

• Una tabla de doble entrada que representa la gramática.<br />

El algoritmo de análisis descendente predictivo para este tipo de analizadores<br />

consiste en ir consultando la tabla para saber que regla aplicar y apoyarse en la pila<br />

27


Asociada:<br />

La gramática NO es LL(1) si y sólo si, existen más de una entrada para cualquier<br />

celda en la tabla.<br />

Gramáticas LL (1)<br />

Se puede aplicar le algoritmo a cualquier gramática G para producir un tabla<br />

de análisis sintáctico M. sin embargo, para alguna gramáticas, M pueden tener<br />

algunas entradas con definiciones múltiples. Por ejemplo, si G es recursiva por la<br />

izquierda o ambigua, entonces M tendrá al menos una entrada con definición<br />

múltiple.<br />

28


Ejemplo.<br />

Left recursion: A veces podemos obtener una gramática LL(k) al remover la “left<br />

recursion”. La idea para “direct left recursion: “ es transformar : A → Aw | Au | Av | a<br />

| b. , en<br />

A → aB | bB<br />

B → wB | uB | vB | Λ.<br />

Ejemplo. Remover la “left recursion. “<br />

S → Sa | b.<br />

Sol: S → bT , T → aT | Λ. Es LL(1).<br />

Left factoring: A veces podemos “left-factor” una gramática LL(k) para obtener una<br />

gramática LL(n) equivalente, donde n < k.<br />

Ejemplo. La gramática S → aaS | ab | b es LL(2) pero no LL(1). Pero si podemos<br />

“factorizar” sobre un prefijo común a desde las producciones S → aaS | ab , para<br />

obtener<br />

S → aT<br />

29


T → aS | b.<br />

Esto da la nueva gramática:<br />

S → aT | b<br />

T → aS | b.<br />

No ambigua, Factorizada por la izquierda y No recursiva a la izquierda.<br />

Decida si es una gramática LL(1), o no.<br />

S → AB | ٨<br />

A → aAb | ٨<br />

B → bB | c.<br />

Solución:<br />

‣ Calculamos los conjuntos FIRST y FOLLOW de las producciones:<br />

30


FIRST(AB) = (FIRST(A) - {٨}) ڂ FIRST(B) = {a} ‏{ڂ b, c} = {a, b, c}.<br />

FIRST(٨) = {٨}, FIRST(aAb) = {a}, FIRST(bB) = {b}, FIRST(c) = {c}.<br />

‣ Calculamos los FOLLOW para los no terminales:<br />

FOLLOW(S) = {$}, FOLLOW(A) = {b, c}, FOLLOW(B) = {$}.<br />

‣ La tabla:<br />

31


Por lo tanto es LL(1), por no tener entradas con definiciones múltiples.<br />

Otra forma, más formal, pero basado en los resultados de FIRST y FOLLOW para<br />

poder aplicar el análisis predictivo LL(1) es necesario que los conjuntos de<br />

predicción de todas las reglas con un mismo antecedente sean disjuntas entre sí.<br />

Esto es,<br />

AÆ abB {a}<br />

AÆ B {b, c}<br />

BÆ b {b}<br />

BÆ c {c}.<br />

Y notamos que {a} ∩ {b,c} = Ǿ es LL(1) , {b} ∩ {c} = Ǿ , es LL(1). Por lo tanto la<br />

gramática en total es LL(1). Pero no perdamos la intuición….. ¿Porque la gramática<br />

es LL(2) pero no LL(1)?<br />

S → aSA | ٨<br />

A → abS | c.<br />

Sol: Consideremos el string aab. Una derivación parte con S ➾ aSA. Ahora el<br />

“lookahead” está en la segunda a de la cadena aab, pero en tal caso, tenemos dos<br />

opciones para escoger, una de ellas es : S → aSA y S → ٨. Entonces, la gramática<br />

no es LL(1). Pero, si consideramos “two lookahead letters” vemos que el substring<br />

ab o ac, se puede lograr con S → aSA para la siguiente etapa.<br />

Analizar si la gramática es LL(1).<br />

32


S → aSC | b<br />

C → cC | d.<br />

Por ejemplo, vemos que el string aabcdd tiene la siguiente “leftmost derivation”,<br />

donde cada etapa está unicamente determinada por el actual símbolo lookahead.<br />

S ➾ aSC ➾ aaSCC ➾ aabCC ➾ aabcCC ➾ aabcdC ➾ aabcdd.<br />

33


Tipos de analizadores<br />

Para comprobar si una cadena pertenece al lenguaje generado por una gramática,<br />

los analizadores sintácticos construyen una representación en forma de árbol de 2<br />

posibles maneras:<br />

– Analizadores sintácticos descendentes (Top-down)<br />

<br />

<br />

Construyen el árbol sintáctico de la raíz (arriba) a las hojas (abajo).<br />

Parten del símbolo inicial de la gramática (axioma) y van expandiendo<br />

producciones hasta llegar a la cadena de entrada.<br />

– Analizadores sintácticos ascendentes (Bottom-up)<br />

<br />

<br />

Construyen el árbol sintáctico comenzando por las hojas.<br />

Parten de los terminales de la entrada y mediante reducciones llegan hasta<br />

el símbolo inicial.<br />

En ambos casos se examina la entrada de izquierda a derecha, analizando los<br />

testigos o tokens de entrada de uno en uno.<br />

<strong>Análisis</strong> <strong>Sintáctico</strong> Descendente - ASD<br />

Métodos que parten del axioma y, mediante derivaciones por la izquierda, tratan de<br />

encontrar la entrada.<br />

<br />

Existen dos formas de implementarlos:<br />

– <strong>Análisis</strong> descendente recursivo<br />

<br />

Es la manera más sencilla, implementándose con una función recursiva<br />

aprovechando la recursividad de la gramática.<br />

– <strong>Análisis</strong> descendente predictivo<br />

<br />

Para aumentar la eficiencia, evitando los retrocesos, se predice en cada<br />

momento cuál de las reglas sintácticas hay que aplicar para continuar el<br />

análisis<br />

34


ASD recursivo<br />

<br />

<br />

<br />

Mediante un método de backtracking se van probando todas las opciones de<br />

expansión para cada no-terminal de la gramática hasta encontrar la correcta.<br />

Cada retroceso en el árbol sintáctico tiene asociado un retroceso en la<br />

entrada: se deben eliminar todos los terminales y no terminales<br />

correspondientes a la producción que se “elimina” del árbol.<br />

Si el terminal obtenido como consecuencia de probar con una opción de las<br />

varias de una producción no coincide con el componente léxico leído en la<br />

entrada, hay que retroceder.<br />

Algoritmo<br />

1. Se colocan las reglas en orden, de forma que si la parte derecha de una<br />

producción es prefijo de otra, esta última se sitúa detrás.<br />

2. Se crea el nodo inicial con el axioma y se considera nodo activo.<br />

3. Para cada nodo activo A:<br />

-Si A es un no-terminal, se aplica la primera producción asociada a A.<br />

• El nodo activo pasa a ser el hijo izquierdo.<br />

• Cuando se terminan de tratar todos los descendientes, el siguiente<br />

nodo activo es el siguiente hijo por la izquierda.<br />

– Si A es un terminal:<br />

• Si coincide el símbolo de la entrada, se avanza el puntero de entrada<br />

y el nodo activo pasa a ser el siguiente “hermano” de A.<br />

• Si no, se retrocede en el árbol hasta el anterior no-terminal (y en la<br />

entrada si se ha reconocido algún componente léxico mediante la<br />

producción que se elimina) y se prueba la siguiente producción.<br />

– Si no hay más producciones para probar, se retrocede hasta el anterior noterminal<br />

y se prueba con la siguiente opción de éste. 4. Si se acaban todas las<br />

opciones del nodo inicial, error sintáctico. Si por el contrario se encuentra un árbol<br />

para la cadena de entrada, éxito.<br />

35


Ejemplo<br />

Comprobar si ccd pertenece al lenguaje de la gramática: S → c X d X → c k | c<br />

ASD recursivo – Problemas<br />

No puede tratar gramáticas con recursividad a izquierdas.<br />

Acaba la ejecución cuando se encuentra el primer error – Difícil proporcionar<br />

mensajes más elaborados que “correcto” o “incorrecto”, como por ejemplo<br />

especificar dónde se ha encontrado el error.<br />

• Aunque la programación es simple, utiliza muchos recursos s<br />

– Como consecuencia del retroceso necesita almacenar los componentes léxicos<br />

ya reconocidos por si es necesario volverlos a tratar.<br />

• Cuando un analizador sintáctico se utiliza para comprobar la semántica y generar<br />

código, cada vez que se expande una regla, se ejecuta una acción semántica. Al<br />

retroceder esa regla o producción se deben deshacer las acciones semánticas, lo<br />

que no es fácil ni siempre posible.<br />

36


ASD predictivo<br />

Intentan predecir la siguiente construcción a aplicar leyendo uno o más<br />

componentes léxicos por adelantado<br />

– “Saben” con exactitud qué regla deben expandir para llegar a la entrada<br />

• Este tipo de analizadores se denomina LL(k)<br />

– Leen la entrada de izquierda a derecha ( Left to rigth)<br />

– Aplican derivaciones por la izquierda para cada entrada ( Left)<br />

– Utilizan k componentes léxicos de la entrada para predecir la dirección del<br />

análisis.<br />

• Están formados por:<br />

– Un buffer para la entrada.<br />

– Una pila de análisis.<br />

– Una tabla de análisis sintáctico.<br />

– Una rutina de control.<br />

37


Componentes de un ASD predictivo<br />

En función de la entrada, de la tabla de análisis y de la pila decide la acción a<br />

realizar.<br />

Posibles acciones: – Aceptar la cadena. – Aplicar producción. – Pasar al siguiente<br />

símbolo de la entrada. – Notificar un error.<br />

Tabla de análisis sintáctico<br />

Se trata de una matriz M [V n, V t ] donde se representan las producciones a<br />

expandir en función del estado actual del análisis y del símbolo de la entrada.<br />

• Las entradas en blanco indican errores<br />

38


Ejemplo<br />

39


40


Manejo de errores<br />

Un compilador es un sistema que en la mayoría de los casos tiene que manejar una<br />

entrada incorrecta. Sobre todo en las primeras etapas de la creación de un<br />

programa, es probable que el compilador se utiliza para efectuar las características<br />

que debería proporcionar un buen sistema de edición dirigido por la sintaxis, es<br />

decir, para determinar si las variables han sido declaradas antes de usarla, o si<br />

faltan corchetes o algo así.<br />

Por lo tanto, el manejo de errores es parte importante de un compilador y el escritor<br />

del compilador siempre debe tener esto presente durante su diseño.<br />

Hay que señalar que los posibles errores ya deben estar considerados al diseñar un<br />

lenguaje de programación. Por ejemplo, considerar si cada proposición del lenguaje<br />

de programación comienza con una palabra clave diferente (excepto la proposición<br />

de asignación, por supuesto). Sin embargo, es indispensable lo siguiente:<br />

El compilador debe ser capaz de detectar errores en la entrada;<br />

<br />

<br />

El compilador debe recuperarse de los errores sin perder demasiada<br />

información;<br />

Y sobre todo, el compilador debe producir un mensaje de error que permita<br />

al programador encontrar y corregir fácilmente los elementos<br />

(sintácticamente) incorrectos de su programa.<br />

• Los errores léxicos incluyen la escritura incorrecta de los identificadores, las<br />

palabras clave o los operadores; por ejemplo, el uso de un identificador tamaño<br />

Elipce en vez de tamaño Elipse, y la omisión de comillas alrededor del texto que se<br />

debe interpretar como una cadena.<br />

• Los errores sintácticos incluyen la colocación incorrecta de los signos de punto<br />

y coma, además de llaves adicionales o faltantes; es decir, “{” o “}”. Como otro<br />

41


ejemplo, en C o Java, la aparición de una instrucción case sin una instrucción switch<br />

que la encierre es un error sintáctico (sin embargo, por lo general, esta situación la<br />

acepta el analizador sintáctico y se atrapa más adelante en el procesamiento,<br />

cuando el compilador intenta generar código).<br />

• Los errores semánticos incluyen los conflictos de tipos entre los operadores y<br />

los operandos. Un ejemplo es una instrucción return en un método de Java, con el<br />

tipo de resultado void.<br />

• Los errores lógicos pueden ser cualquier cosa, desde un razonamiento<br />

incorrecto del programador en el uso (en un programa en C) del operador de<br />

asignación =, en vez del operador de comparación ==. El programa que contenga =<br />

puede estar bien formado; sin embargo, tal vez no refleje la intención del<br />

programador.<br />

La precisión de los métodos de análisis sintáctico permite detectar los errores<br />

sintácticos con mucha eficiencia. Varios métodos de análisis sintáctico, como los<br />

métodos LL y LR, detectan un error lo más pronto posible; es decir, cuando el flujo<br />

de tokens que proviene del analizador léxico no puede seguirse analizando de<br />

acuerdo con la gramática para el lenguaje. Dicho en forma más precisa, tienen la<br />

propiedad de prefijo viable, lo cual significa que detectan la ocurrencia de un error<br />

tan pronto como ven un prefijo de la entrada que no puede completarse para formar<br />

una cadena válida en el lenguaje.<br />

Otra de las razones para enfatizar la recuperación de los errores durante el análisis<br />

sintáctico es que muchos errores parecen ser sintácticos, sea cual fuere su causa,<br />

y se exponen cuando el análisis sintáctico no puede continuar. Algunos errores<br />

semánticos, como los conflictos entre los tipos, también pueden detectarse con<br />

eficiencia; sin embargo, la detección precisa de los errores semánticos y lógicos en<br />

tiempo de compilación es, por lo general, una tarea difícil.<br />

El mango de errores en un analizador sintáctico tiene objetivos que son simples de<br />

declarar, pero difíciles de llevar a cabo:<br />

Reportar la presencia de errores con claridad y precisión.<br />

• Recuperarse de cada error lo bastante rápido como para poder detectar los errores<br />

42


siguientes.<br />

• Agregar una sobrecarga mínima al procesamiento de los programas correctos.<br />

Por fortuna, los errores comunes son simples, y a menudo basta con un mecanismo<br />

simple para su manejo.<br />

Estrategias para recuperarse de los errores<br />

Una vez que se detecta un error, ¿cómo debe recuperarse el analizador sintáctico?<br />

Aunque no hay una estrategia que haya demostrado ser aceptable en forma<br />

universal, algunos métodos pueden aplicarse en muchas situaciones. El método<br />

más simple es que el analizador sintáctico termine con un mensaje de error<br />

informativo cuando detecte el primer error. A menudo se descubren errores<br />

adicionales si el analizador sintáctico puede restaurarse a sí mismo, a un estado en<br />

el que pueda continuar el procesamiento de la entrada, con esperanzas razonables<br />

de que un mayor procesamiento proporcione información útil para el diagnóstico. Si<br />

los errores se apilan, es mejor para el compilador desistir después de exceder cierto<br />

límite de errores, que producir una molesta avalancha de errores “falsos”.<br />

El resto de esta sección se dedica a las siguientes estrategias de recuperación de<br />

los errores: modo de pánico, nivel de frase, producciones de errores y corrección<br />

global.<br />

Recuperación en modo de pánico Con este método, al describir un error el<br />

analizador sintáctico descarta los símbolos de entrada,<br />

uno a la vez, hasta encontrar un conjunto designado de tokens de sincronización.<br />

Por lo general, los tokens de sincronización son delimitadores como el punto y coma<br />

o }, cuya función en el programa fuente es clara y sin ambigüedades. El diseñador<br />

del compilador debe seleccionar los tokens de sincronización apropiados para el<br />

lenguaje fuente. Aunque la corrección en modo de pánico a menudo omite una<br />

cantidad considerable de entrada sin verificar errores adicionales, tiene la ventaja<br />

43


de ser simple y, a diferencia de ciertos métodos que consideraremos más adelante,<br />

se garantiza que no entrará en un ciclo infinito.<br />

Recuperación a nivel de frase<br />

Al descubrir un error, un analizador sintáctico puede realizar una corrección local<br />

sobre la entrada restante; es decir, puede sustituir un prefijo de la entrada restante<br />

por alguna cadena que le permita continuar. Una corrección local común es sustituir<br />

una coma por un punto y coma, eliminar un punto y coma extraño o insertar un punto<br />

y coma faltante. La elección de la corrección local se deja al diseñador del<br />

compilador. Desde luego que debemos tener cuidado de elegir sustituciones que no<br />

nos lleven hacia ciclos infinitos, como sería, por ejemplo, si siempre insertáramos<br />

algo en la entrada adelante del símbolo de entrada actual.<br />

La sustitución a nivel de frase se ha utilizado en varios compiladores que reparan<br />

los errores, ya que puede corregir cualquier cadena de entrada. Su desventaja<br />

principal es la dificultad que tiene para arreglárselas con situaciones en las que el<br />

error actual ocurre antes del punto de detección.<br />

Producciones de errores<br />

Al anticipar los errores comunes que podríamos encontrar, podemos aumentar la<br />

gramática para el lenguaje, con producciones que generen las construcciones<br />

erróneas. Un analizador sintáctico construido a partir de una gramática aumentada<br />

por estas producciones de errores detecta los errores anticipados cuando se utiliza<br />

una producción de error durante el análisis sintáctico. Así, el analizador sintáctico<br />

puede generar diagnósticos de error apropiados sobre la construcción errónea que<br />

se haya reconocido en la entrada.<br />

Corrección<br />

global<br />

Lo ideal sería que un compilador hiciera la menor cantidad de cambios en el<br />

procesamiento de una cadena de entrada incorrecta. Hay algoritmos para elegir una<br />

secuencia mínima de cambios, para obtener una corrección con el menor costo a<br />

nivel global. Dada una cadena de entrada incorrecta x y una gramática G, estos<br />

44


algoritmos buscarán un árbol de análisis sintáctico para una cadena y relacionada,<br />

de tal forma que el número de inserciones, eliminaciones y modificaciones de los<br />

tokens requeridos para transformar a x en y sea lo más pequeño posible. Por<br />

desgracia, estos métodos son en general demasiado costosos para implementarlos<br />

en términos de tiempo y espacio, por lo cual estas técnicas sólo son de interés<br />

teórico en estos momentos. Hay que observar que un programa casi correcto tal vez<br />

no sea lo que el programador tenía en mente. Sin embargo, la noción de la<br />

corrección con el menor costo proporciona una norma para evaluar las técnicas de<br />

recuperación de los errores, la cual se ha utilizado para buscar cadenas de<br />

sustitución óptimas para la recuperación a nivel de frase.<br />

45


Generadores de analizadores sintácticos<br />

ANTLR:<br />

(ANother Tool for Language Recognition; en español "otra herramienta para<br />

reconocimiento de lenguajes") es una herramienta creada principalmente por<br />

Terence Parr, que opera sobre lenguajes, proporcionando un marco para construir<br />

reconocedores (parsers), intérpretes, compiladores y traductores de lenguajes a<br />

partir de las descripciones gramaticales de los mismos (conteniendo acciones<br />

semánticas a realizarse en varios lenguajes de programación).<br />

GNU bison:<br />

Es un programa generador de analizadores sintácticos de propósito general<br />

perteneciente al proyecto GNU disponible para prácticamente todos los sistemas<br />

operativos, se usa normalmente acompañado de flex aunque los analizadores<br />

léxicos se pueden también obtener de otras formas.<br />

Grammatica:<br />

Es un generador de analizadores sintácticos de C# y Java libre. Es similar a otras<br />

herramientas como Yacc o ANTLR. Grammatica soporta el algoritmo LL(k) para<br />

gramáticas con un número ilimitado de tokens de anticipación. Está bastante bien<br />

probado, y ha sido auto compilado desde la versión 0.1. La documentación contiene<br />

una lista completa de características, así como una comparación con otros<br />

generadores de analizadores.<br />

JavaCC:<br />

(Java Compiler Compiler) es un generador de analizadores sintácticos de código<br />

abierto para el lenguaje de programación Java. JavaCC es similar a Yacc en que<br />

genera un parser para una gramática presentada en notación BNF, con la diferencia<br />

de que la salida es en código Java. A diferencia de Yacc, JavaCC genera<br />

analizadores descendentes (top-down), lo que lo limita a la clase de gramáticas LL<br />

(K) (en particular, la recursión desde izquierda no se puede usar). El constructor de<br />

árboles que lo acompaña, JJTree, construye árboles de abajo hacia arriba (bottomup).<br />

Yacc:<br />

Es un programa para generar analizadores sintácticos. Las siglas del nombre<br />

significan Yet Another Compiler-Compiler, es decir, "Otro generador de<br />

compiladores más". Genera un analizador sintáctico (la parte de un compilador que<br />

comprueba que la estructura del código fuente se ajusta a la especificación<br />

sintáctica del lenguaje) basado en una gramática analíticaescrita en una notación<br />

similar a la BNF. Yacc genera el código para el analizador sintáctico en el Lenguaje<br />

de programación C.<br />

46


Generador sintáctico GNU BISON<br />

GNU bison es un programa generador de analizadores sintácticos de propósito<br />

general perteneciente al proyecto GNU disponible para prácticamente todos los<br />

sistemas operativos, se usa normalmente acompañado de flex<br />

GNU bison tiene compatibilidad con Yacc: todas las gramáticas bien escritas para<br />

Yacc, funcionan en Bison sin necesidad de ser modificadas. Cualquier persona que<br />

esté familiarizada con Yacc podría utilizar Bison sin problemas. Bison fue escrito en<br />

un principio por Robert Corbett; Richard Stallman lo hizo compatible con Yacc y<br />

Wilfred Hansen de la Carnegie Mellon University añadió soporte para literales<br />

multicaracter y otras características.<br />

1. En Bison se declaran los Token, que serían los no terminales de la gramática.<br />

Hay tres formas de declarar Tokens.<br />

2. Un carácter entre comillas simples, este no es necesario declararlo, se puede<br />

utilizar directamente en la gramática y se reconoce como Token, si se desea<br />

darle algún valor semántica, entonces si se tendría que declarar.<br />

%left o %right para definir la asociatividad de los operadores.<br />

47


Conclusión<br />

Se puede decir que con toda la información recabada ya se tiene en claro que es<br />

un analizador sintáctico, como armar un árbol de derivación y saber cuáles son sus<br />

propiedades en fin y a se puede Identificar y conocer el funcionamiento de un<br />

analizador sintáctico, hay que tomar muy en cuenta los errores y los tipos de errores<br />

que se pueden presentar en el compilador, hay que señalar que los posibles errores<br />

ya deben de estar considerados al diseñar un lenguaje de programación. Considerar<br />

su cada proposición del lenguaje de programación comienza con una ´palabra clave<br />

referente. Y pues se vio varios generadores de analizador sintáctico cada una de la<br />

mencionada maneja un lenguaje de programación.<br />

48


Bibliografías<br />

‣ Alfred V. Aho Monica S. Lam Ravi Sethi Jeffrey D. Ullman. (2008).<br />

Compiladores principios, técnicas y herramientas, Segunda edición. México:<br />

Pearson Educación.<br />

‣ Profs. Carlos Pérez y Ricardo Monascal. Traductores e interprestes. 20 de<br />

junio del 2016.<br />

http://ldc.usb.ve/~rmonascal/cursos/ci3725_aj12/archivos/clase8.pdf<br />

‣ Dr. José Antonio Camarena Ibarrola.Enero 2009.LENGUAJES FORMALES<br />

Y AUTOMATAS. 19 de junio del 2016.<br />

http://dep.fie.umich.mx/~camarena/NotasLenguajesFormalesAutomatas_Ca<br />

marena.doc<br />

‣ Sergio Gálvez Rojas. David Tinaquero Fernández. Antonio Guevara Plaza.<br />

Antonio Luis Carrillo León.GENERACIÓN COMPLETA DE COMPILADORES<br />

‣ MEDIANTE DIAGRAMAS DE SINTAXIS EXTENDIDOS. 19 de junio del<br />

2016. http://www.lcc.uma.es/repository/fileDownloader?rfname=LCC829.pdf<br />

‣ Salvador Sánchez Alonso, Daniel Rodríguez García. PROCESADORES DE<br />

LENGUAJES. 19 de junio del 2016.<br />

http://www.cc.uah.es/ie/docencia/ProcesadoresDeLenguaje/ProcesadoresD<br />

eLenguajeTema3ParteI_1xpagina.pdf<br />

‣ Efren Mendez Hernandez. sep 18/2015. Lenguajes automatas 1. 19 de junio<br />

del 2016. http://documents.mx/documents/reporte-unidad-6.html<br />

49

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!