18
Papel del analizador sintáctico El analizador sintáctico obtiene una cadena de componentes léxicos del analizador léxico, y comprueba si la cadena puede ser generada por la gramática del lenguaje fuente, el analizador sintáctico deberá informar cualquier error. La principal tarea del analizador sintáctico (o parser) no es comprobar que la sintaxis del programa fuente sea correcta, sino construir una representación interna de ese programa y , en el caso de que sea un programa incorrecto, dar un mensaje de error. Tipos de Analizadores Sintácticos Métodos universales: Pueden analizar cualquier gramática, pero son demasiado ineficientes. Los métodos utilizados generalmente para compiladores son: Ascendentes Construyen árboles sintácticos a partir de las hojas y suben a la raíz Descendentes Construyen árboles sintácticos de la raíz a las hojas En ambos casos se examina la entrada al A.S. de izquierda a derecha, un símbolo a la vez.

compiladores (1)

  • Upload
    melissa

  • View
    249

  • Download
    0

Embed Size (px)

Citation preview

Papel del analizador sintácticoEl analizador sintáctico obtiene una cadena de componentes léxicos del analizador léxico, y comprueba si la cadena puede ser generada por la gramática del lenguaje fuente, el analizador sintáctico deberá informar cualquier error.

La principal tarea del analizador sintáctico (o parser) no es comprobar que la sintaxis del programa fuente sea correcta, sino construir una representación interna de ese programa y, en el caso de que sea un programa incorrecto, dar un mensaje de error.

Tipos de Analizadores Sintácticos

Métodos universales:Pueden analizar cualquier gramática, pero son demasiado ineficientes.

Los métodos utilizados generalmente para compiladores son: Ascendentes

Construyen árboles sintácticos a partir de las hojas y suben a la raíz

Descendentes

Construyen árboles sintácticos de la raíz a las hojas En ambos casos se examina la entrada al A.S. de izquierda a derecha, un símbolo a la vez.

Manejo de errores sintácticosEl manejador de errores debe informar de la presencia de un error, indicando el lugar preciso en el programa, y si sabe cuál es el error, se incluye un mensaje, una de la razón de muchos errores son los de naturaleza sintáctica o se manifiestan cuando la cadena de componentes léxicos que

proviene del analizador léxico desobedece las reglas gramaticales que definen al lenguaje de programación.

Ejemplo de errores sintácticos:

Léxicos: como escribir mal un identificador, palabra clave u operador. Sintácticos: como una expresión aritmética con paréntesis no equilibrados. Semánticos: como un operador aplicado a un operando incompatible. Lógico: como una llamada infinitamente recursiva.

El manejador de errores del analizador sintáctico tiene los siguientes objetivos:

Debe informar de la presencia de errores con claridad. Debe recuperar cada error con rapidez para detectar errores posteriores. No debe retrasar el procesamiento de programas correctos.

Estrategia de recuperación de errores En modo de pánico: este al descubrir un error, el analizador sintáctico desecha símbolos

de entrada, de uno en uno hasta encontrar uno perteneciente al conjunto designado de componentes léxicos.

A nivel de frase: al descubrir un error, el analizador sintáctico puede realizar una corrección local de la entrada restante, puede sustituir un prefijo por alguna cadena que permita continuar al analizador sintáctico.

De producciones de error: se utiliza para construir el analizador sintáctico, esta puede generar diagnóstico de error apropiados para indicar la construcción errónea reconocida en la entrada.

De corrección global: esta técnica en la actualidad solo son de interés teórico.

Gramáticas independientes del contexto

Muchas construcciones de los lenguajes de programación tienen una estructura inherentemente recursiva que se puede definir mediante gramáticas independientes del contexto.

Ejemplo:

Prop → if expr then prop else prop

Una GIC consta de terminales, no terminales, un símbolo inicial y producciones.

1. Terminales: son los símbolos básicos con que se forman las cadenas. ”componente léxico” es sinónimo de terminal para lenguajes de programación. Cada una de las palabras if, then y else es un terminal.

2. No terminales: definen conjuntos de cadenas que ayudan a definir el lenguaje generado por la gramática. También imponen una estructura jerárquica sobre el lenguaje que es útil para el análisis sintáctico y para la traducción. Prop y expr son no terminales.

3. Símbolo inicial: es el conjunto de cadenas que representa el lenguaje definido por la gramática.

4. Producciones: especifica como se pueden combinar los terminales y los no terminales para formar cadenas.

Convenciones de notaciónPara evitar tener que establecer siempre los terminales y los no terminales, a partir de ahora se emplearan las siguientes convenciones de notación con respecto a las gramáticas.

1. Para símbolos terminales:a. Las primeras letras minúsculas del alfabeto, como a,b,c..b. Los símbolos de operador, como +, -, etc..c. Los símbolos de puntuación como() , etc..d. Los dígitos como 0,1,…,9.e. Cadenas en negritas, como id o if.

2. Para símbolos no terminales:a. Las primeras letras del alfabeto, A,B,C..b. La letra S, que cuando aparece suele ser símbolo inicial.c. Los nombres en cursiva minúscula, como expr o prop

3. X, Y, Z, representan símbolos gramaticales, es decir, terminales o no terminales.4. Las ultimas letras minúsculas del alfabeto como, u, v,…, z, representan cadenas de

terminales.5. Letras griegas minúsculas como,α, β, Representan cadenas de símbolos gramaticales.6. Producciones alternativas.7. A menos que se diga otra cosa, el lado izquierdo de la primera producción es el símbolo

inicial.

DerivacionesUna gramática define a un lenguaje; se considera que un árbol de análisis sintáctico es un ejemplo de derivación. La idea central de derivación es la que se considera una producción como una regla de reescritura, donde el no terminal dela izquierda es sustituido por la cadena del lado derecho dela producción.Ejemplo:-(id + id)

Árbol de análisis sintáctico y derivacionesUn árbol de análisis sintáctico se puede considerar como una representación gráfica de una derivación que no muestra la elección relativa al orden de sustitución.

las hojas del árbol de análisis sintáctico se etiquetan con terminales o no terminales y, leídas de izquierda a derecha, construyen una forma de frase, llamada el producto o frontera del árbol.

Ejemplo:

La frase id + id*id tiene las dos colaras derivaciones por la izquierda.

AmbigüedadEs una gramática que produce más de un árbol de análisis sintáctico para alguna frase. Para muchos es preferible que la gramática no sea ambigua, pues así no se podría determinarse de manera exclusiva que árbol de análisis sintáctico seleccionar para una frase, eliminando la ambigüedad se desechan arboles de análisis sintáctico indeseables, dejando solo un árbol para cada frase.

Escritura de una gramáticaLas gramáticas son capaces de escribir la mayoría, pero no todas, de las sintaxis de los lenguajes de programación. Un analizador léxico efectuar una cantidad limitada de análisis sintáctico conforme produce la secuencia de componentes léxicos a partir de los caracteres de entrada.

Expresiones regulares, o gramáticas independientes del contexto

Toda construcción que se pueda describir mediante una expresión regular también se puede describir por medio de una gramática. Por ejemplo, la expresión regular (aIb)*abb y la gramática

Describen el mismo lenguaje, el conjunto de cadenas de caracteres a y b que terminan en abb.

¿Por qué utilizar expresiones regulares para definir la sintaxis lexicográfica de un lenguaje? Existen varias razones.

1. las reglas lexicográficas son bastante sencillas, y para describirlas no se necesita una notación tan poderosa como las gramáticas.

2. Las ER por lo general proporcionan una notación mas fácil de entender para los componentes léxicos que una gramática.

3. Se puede construir automáticamente analizadores léxicos mas eficientes a partir de expresiones regulares que de gramáticas arbitrarias.

4. Separar la estructura sintáctica de un lenguaje en partes léxicas.

Comprobación de lenguaje generado por una gramática

Es importante ser capaz de razonar que un conjunto dado de producciones genera un lenguaje determinado.

Una prueba de que una gramática G genera un lenguaje L tiene dos partes: se debe demostrar que toda cadena generada por G esta en L, y lo opuesto,, que toda cadena de L puede de hecho ser generada por G.

Supresión de la ambigüedadA veces una gramática ambigua se puede reescribir para eliminar la ambigüedad.

Eliminación de la recursión por la izquierdaUna gramática es recursiva por la izquierda si tiene un no terminal A tal que existe una derivación a⟹ Aa para alguna cadena α. Los métodos de análisis sintáctico descendente no pueden manejar gramáticas recursivas por la izquierda, asi que se necesita una transformación que elimine la recursión por la izquierda.

Ejemplo:

A → Aα I β puede sustituirse por

Construcciones de lenguajes no independientes del contexto

Algunos lenguajes no pueden ser generados por ninguna gramática, se deben de utilizar lenguajes abstractos sencillos para ilustrar las dificultades.

Análisis sintáctico descendenteSe introducen las ideas básicas del análisis sintáctico descendente y se enseña a construir una forma eficiente sin retroceso de un analizador sintáctico descendente llamado analizador sintáctico predictivo. Se define la clase de gramáticas LL, a partir de las cuales se pueden construir de manera automática analizadores sintácticos predictivos.

Análisis sintáctico por descenso recursivoEs un intento de encontrar una derivación por la izquierda para una cadena de entrada. También se puede considerar como intento de construir un árbol de análisis sintáctico para la entra comenzando desde la raíz y creando los nodos del árbol en orden previo.

Una gramática recursiva por la izquierda puede hace que un analizador sintáctico por descenso recursivo, incluso uno con retroceso, entre en un lazo infinito, es decir, cuando se intenta expandir A, puede que de nuevo se este intentado expandir A sin haber consumido ningún símbolo de entrada.

Analizadores sintácticos predictivosEn muchos casos, escribiendo con cuidado una gramática, eliminando su recursión por la izquierda y factorizando por la izquierda la gramática resultante, se puede obtener una gramática analizable con un analizador sintáctico por descenso recursivo que no necesite retroceso, es decir, una analizador sintáctico predictivo. Para construir un analizador sintáctico predictivo, se debe conocer, dado el símbolo actual de entrada y el no terminal a expandir.

Diagrama de transiciones par analizadores sintácticos predictivosUn diagrama de transiciones es un plan o diagrama de flujo útil para un analizador léxico, se puede crear un diagrama de transiciones como plan para un analizador sintáctico predictivo.Para construir el diagrama de transiciones de un analizador sintáctico predictivo a partir de una gramática, primero se debe eliminar la recursión por la izquierda de la gramática, y después factorizar dicha gramática por la izquierda, luego para cada no terminal A se hace lo siguiente:

1. Créese un estado inicial y un estado final (de retorno)2. Para cada producción A →X1, X2, …, Xn créese un camino desde el estado inicial al

estado final, con aristas etiquetadas con A →X1, X2, …, Xn.

Análisis sintáctico predictivo no recursivoSe puede construir un análisis sintáctico predictivo no recursivo haciendo una pila, mediante llamadas recursivas, el problema clave durante el análisis sintáctico predictivo es determinar la producción que se debe aplicar e un no terminal. A continuación vera como se puede construir directamente la tabla a partir de ciertas gramáticas.

Un analizador sintáctico predictivo guiado por tablas tiene un buffer de entrada, una pila, una tabla de análisis sintáctico y una cadena de salida. El buffer de entrada contiene la cadena que se va a analizar, seguida de un símbolo utilizado como delimitador derecho para indicar el fin de la

cadena de entrada. La pila contiene una secuencia de símbolos gramaticales en la parte de abajo, que indica la base de la pila.

Ejemplo:

Id + id * id

Primero y siguientePermiten rellenar, siempre que sea posible, las entradas de una tabla de análisis sintáctico predictivo para G. también se pueden utilizar los conjuntos de componentes léxicos devueltos por la función siguiente como componentes léxicos de sincronización durante la recuperación de errores en modo de pánico.

Para calcular primero para todos los símbolos gramaticales X aplíquense las reglas siguientes hasta qu no se puedan añadir mas terminales o ɛ a ningún conjunto primero.

1. Si X es terminal, entonces PRIMERO (X) es (X).2. Si X → ɛ es una producción, entonces añádase ɛ a primero.

Para calcular SIGUIENTE(A) para todos los no terminales A, aplíquense las reglas siguientes hasta que no se pueda añadir nada mas a ningún conjunto siguiente.

1. Póngase $ en SIGUIETES ($), donde S es el símbolo inicial y $ es el delimitador derecho de la entrada.

2. Si hay una producción a → aBβ, entonces todo lo que este en PRIMERO (β) excepto ɛ se pone en SIGUIENTE(b).

3. Si hay una producción A → αB o una producción A → αBβ, donde PRIMERO(B) contenga ɛ (es decir β ⇒ ɛ ), entonces todo lo que este en SIGUIENTE(A) se pone en SIGUIENTE(B).

Ejemplo:

Construcción de Tablas de análisis sintácticoSe puede utilizar el siguiente algoritmo para construir una tabla de análisis sintáctico predictivo para una gramática G. la idea en que se basa el algoritmo es la siguiente. Suponiendo que A → α es una producción con a en PRIEMERO(a). Entonces el analizador sintáctico expandirá A por α cuando el símbolo actual de la entrada sea α. La única complicación surge cuando α = ɛ o a⇒∗¿ en este caso, se debe expandir de nuevo A en α si el símbolo actual de la entrada esta en SIGUIENTE(A), o si ya se ha alcanzado en $ de la entrada $ esta en SIGUIENTE(A).

ALGORITMO

Construcción de una tabla de análisis sintáctico predictivo.

Entrada: una gramática G. Salida: la tabla de análisis sintáctico M.

Gramática LL(1)Se puede aplicar el algoritmo anterior a cualquier gramática G para producir una tabla de análisis sintáctico M. sin embargo, para algunas gramáticas, M puede tener algunas entradas con definiciones múltiples. Por ejemplo, si G es recursiva por la izquierda o ambigua, entonces M tendrá al menos una entrada con definición múltiple.Ejemplo:

Una gramática cuya tabla de análisis sintáctico no tiene entradas con definiciones múltiples se define como LL(I). La primera L de LL(I) representa (por left, en ingles izquierda) el examen de la entrada de izquierda a derecha, la segunda L representa una derivación por la izquierda, y el I es por utilizar un símbolo de entrada de examen por anticipado a cada paso para tomar las decisiones de accionen el análisis sintáctico. Se puede demostrar que el algoritmo anterior produce para toda gramática G en forma LL(I) una tabla de análisis sintáctico que analiza todas, y exclusivamente, las frases G.

Las gramáticas LL(I) tienen varias propiedades distintivas:

1. Para ningún terminal a tanto α como β derivan a la vez cadenas que comience con a.2. A lo sumo una de α y β puede derivar la cadena vacía.3. Si β ⇒ɛ, α no deriva ninguna cadena que comience con un terminal en SIGUIENTE(A).

RECUPERACIO DE ERRORES EN EL ANALISIS SINTACTICO PREDICTIVO

La pila de un analizador sintáctico no recursivo hace explícitos los terminales y no terminales que el analizador espera emparejar con el resto de la entrada. Durante el análisis sintáctico predictivo se detecta un error cuando el terminal de la cima de la pila no concuerda con el

siguiente símbolo de entrada o cuando el no terminal A esta en la cima de la pila, α es el siguiente símbolo de entrada y la entrada M[A, a] de la tabla de análisis sintáctico esta vacía.

ANALISIS SINTACTICO PERMANENTEEl análisis sintáctico por desplazamiento y reducción intenta construir un árbol de análisis sintáctico para una cadena de entrada que comienza por las hojas (el fondo) y avanza hacia la raíz. Se puede considerar este proceso como de “reducir” una cadena W al símbolo inicial de la gramática. En cada paso de reducción se sustituye una su cadena determinada que concuerde con el lado derecho de una producción por el símbolo del lado izquierdo de dicha producción y si en cada paso se elige correctamente la subcadena, se traza una derivación por la derecha en sentido inverso.

Ejemplo:

Mangos

Informalmente, un “mango” de una cadena es una subcadena que concuerda con el lado derecho de una producción y cuya reducción al no terminal del lado izquierdo de la producción representa un paso a lo largo de la inversa de una derivación a la derecho.

Ejemplo:

PODA

Se puede obtener una derivación por la derecha en orden inverso mediante la “la poda de mangos”. Es decir, comienza con una cadena de terminales w que se desee analizar sintácticamente. Ejemplo:

ANALISIS SINTACTICO POR PRECEDENCIA E OPERADORES

La mayor parte de clase de gramáticas para las que se pueden construir con éxito analizadores sintácticos por desplazamiento y reducción (las gramáticas L.R.) Sin embargo, para una pequeña, pero importante, clase de gramáticas, se pueden construir con facilidad a mano eficientes analizadores sintácticos por desplazamiento y reducción. Estas gramáticas tienen la propiedad de que ningún lado derecho de la producción es € ni tiene dos no terminales adyacentes. Una gramática con esta ultima propiedad se denomina gramática de operadores.

ANALIZADORES SINTACTICOS LR

Esta es una técnica eficiente de análisis sintáctico ascendente que se puede utilizar para analizar una clase mas amplia de gramáticas independientes de contexto. La técnica se denomina análisis sintáctico LR(k); la “L” es por el examen de la entrada de izquierda a derecha, la }”R” por construir una derivación por la derecha en donde inverso, y la k por el numero de simbolos de entrada de examen por anticipado utilizados para tomar las decisiones del análisis sintáctico. Cuando se omite, se asume que k, es 1. El análisis sintáctico LR es atractivo por varias razones.

Se pueden construir analizadores sintácticos LR para reconocer prácticamente todas las costrucciones de los lenguajes de programación para los que se pueden escribir gramáticas independientes del contexto.

El método de análisis sintáctico LE es el método de análisis por desplazamiento y reducción sin retroceso mas general que se conoce, y sin embargo se puede aplicar tan eficientemente como los otros métodos de desplazamiento y reducción.

La clase de gramáticas que pueden analizarse con los métodos LR es un supraconjunto de la clase de gramáticas que se pueden analizar con analizadores sintácticos predictivos.