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:
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
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.
Á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>