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