1. PONTIFICIA UNIVERSIDAD CATOLICA DEL ECUADOR
SEDE IBARRA
Nombre: Alexis Vilañez
Fecha: 03/05/13
Materia: Compiladores
ANALIZADOR SINTACTICO
El papel del analizador sintáctico:
El análisis sintáctico convierte el texto de entrada en otras estructuras (comúnmente árboles), que
son más útiles para el posterior análisis y capturan la jerarquía implícita de la entrada. Un
analizador léxico crea tokens de una secuencia de caracteres de entrada y son estos tokens los que
son procesados por el analizador sintáctico para construir la estructura de datos, por ejemplo un
árbol de análisis o árboles de sintaxis abstracta.
El análisis sintáctico también es un estado inicial del análisis de frases de lenguaje natural. Es
usado para generar diagramas de lenguajes que usan flexión gramatical, como los idiomas
romances o el latín. Los lenguajes habitualmente reconocidos por los analizadores sintácticos son
los lenguajes libres de contexto. Cabe notar que existe una justificación formal que establece que
los lenguajes libres de contexto son aquellos reconocibles por un autómata de pila, de modo que
todo analizador sintáctico que reconozca un lenguaje libre de contexto es equivalente en
capacidad computacional a un autómata de pila.
Los analizadores sintácticos fueron extensivamente estudiados durante los años 70 del siglo XX,
detectándose numerosos patrones de funcionamiento en ellos, cosa que permitió la creación de
programas generadores de analizadores sintáticos a partir de una especificación de la sintaxis del
lenguaje en forma Backus-Naur por ejemplo, tales como yacc, GNU bison y javaCC.
Análisis sintáctico ascendente y descendente
La tarea esencial de un analizador es determinar si una determinada entrada puede ser
derivada desde el símbolo inicial, usando las reglas de una gramática formal, y como hacer
esto, existen esencialmente dos formas:
2. Analizador sintáctico descendente (Top-Down-Parser): ..un analizador puede empezar con el
símbolo inicial e intentar transformarlo en la entrada, intuitivamente esto sería ir dividiendo la
entrada progresivamente en partes cada vez más pequeñas, de esta forma funcionan los
analizadores LL, un ejemplo es el javaCC.
Analizador sintáctico ascendente (Bottom-Up-Parser): un analizador puede empezar con la
entrada e intentar llegar hasta el símbolo inicial, intuitivamente el analizador intenta
encontrar los símbolos más pequeños y progresivamente construir la jerarquía de símbolos
hasta el inicial, los analizadores LR funcionan así y un ejemplo es el Yacc.
Otros tipos de analizadores son:
Analizador sintáctico descendente recursivo
Chart parser
Left corner parser
Analizador sintáctico LR
De la forma de construir el árbol sintáctico se desprenden dos tipos o clases de
analizadores sintácticos. Pueden ser descendentes o ascendente
• Descendentes : Parten del axioma inicial, y van efectuando derivaciones a izquierda hasta
obtener la secuencia de derivaciones que reconoce a la sentencia.
Pueden ser:
Con retroceso.
Con recursión.
LL(1)
• Ascendentes: Parten de la sentencia de entrada, y van aplicando reglas de producción
hacia atrás (desde el consecuente hasta el antecedente), hasta llegar al axioma inicial.
Pueden ser:
Con retroceso.
LR(1)
Análisis semántico y tratamiento de errores
Si un compilador tuviera que procesar sólo programas correctos, su diseño e implantación
se simplificarían mucho. Pero los programadores a menudo escriben programas
incorrectos, y un buen compilador debería ayudar al programador a identificar y localizar
errores. Es más, considerar desde el principio el manejo de errores puede simplificar la
estructura de un compilador y mejorar su respuesta a los errores.
Los errores en la programación pueden ser de los siguientes tipos:
• Léxicos, producidos al escribir mal un identificador, una palabra clave o un
operador.
• Sintácticos, por una expresión aritmética o paréntesis no equilibrados.
• Semánticos, como un operador aplicado a un operando incompatible.
• Lógicos, puede ser una llamada infinitamente recursiva.
El manejo de errores de sintaxis es el más complicado desde el punto de vista de la
3. creación de compiladores. Nos interesa que cuando el compilador encuentre un error, se
recupere y siga buscando errores. Por lo tanto el manejador de errores de un analizador
sintáctico debe tener como objetivos:
• Indicar los errores de forma clara y precisa. Aclarar el tipo de error y su
localización.
• Recuperarse del error, para poder seguir examinando la entrada.
• No ralentizar significativamente la compilación.
Un buen compilador debe hacerse siempre teniendo también en mente los errores que se
pueden producir; con ello se consigue:
• Simplificar la estructura del compilador.
• Mejorar la respuesta ante los errores.
Tenemos varias estrategias para corregir errores, una vez detectados:
• Ignorar el problema (Panic mode ): Consiste en ignorar el resto de la entrada
hasta llegar a una condición de seguridad. Una condición tal se produce cuando
nos encontramos un token especial (por ejemplo un ‘;’ o un ‘END’).A partir de
este punto se sigue analizando normalmente.
Ejemplo:
aux = a[i]
a[i] = a[j];
a[j] = aux;
Error
id ‘=’ id ‘*‘ id ‘+’ id ’*‘ id ‘+’ ’=’ id ’*‘ id ‘+’ ‘;’ id ’*‘ id ‘+’ ‘=’ id ‘;’
Token especial, seguimos
compilando a partir de él
• Recuperación a nivel de frase: Intenta recuperar el error una vez descubierto. En
el caso anterior, por ejemplo, podría haber sido lo suficientemente inteligente
como para insertar el token ‘;’ . Hay que tener cuidado con este método, pues
puede dar lugar a recuperaciones infinitas.
• Reglas de producción adicionales para el control de errores: La gramática se
puede aumentar con las reglas que reconocen los errores más comunes. En el caso
anterior, se podría haber puesto algo como:
sent_errónea Ú sent_sin_acabar sentencia_acabada ’;’
sentencia_acabada Ú sentencia ‘;’
sent_sin_acabar Ú sentencia
Lo cual nos da mayor control en ciertas circunstancias
• Corrección Global : Dada una secuencia completa de tokens a ser reconocida, si
hay algún error por el que no se puede reconocer, consiste en encontrar la
secuencia completa más parecida que sí se pueda reconocer. Es decir, el
analizador sintáctico le pide toda la secuencia de tokens al léxico, y lo que hace
es devolver lo más parecido a la cadena de entrada pero sin errores, así como el
árbol que lo reconoce.
4. Árboles Sintácticos
Es una representación que se utiliza para describir el proceso de derivación de dicha
sentencia.
Como nodos internos del árbol, se sitúan los elementos no terminales de las reglas de
producción que vayamos aplicando, y tantos hijos como símbolos existan en la parte
derecha de dichas reglas.
Veamos un ejemplo: Sea la gramática anterior.
E Ú E + T | T
T Ú T * F | F
F Ú ( E ) | a | b
Supongamos que hay que reconocer: ( a + b ) * a + b
Si el árbol puede construirse, es que la sentencia es correcta:
Ambigüedad: Una gramática es ambigua si derivando de forma diferente con el mismo
tipo de derivación se llega al mismo resultado.
Ejemplo: Considérese la gramática
E E + E
E E * E
E ( E )
E id | num
si tenemos aux + cont + i
<ID><+><ID><+><ID>