Análisis sintáctico.

Anuncio
PROGRAMACION DE SISTEMAS I
UNIDAD IV. ANALISIS SINTACTICO
(Primera parte)
Todo lenguaje de programación tiene reglas que prescriben la estructura
sintáctica de programas bien formados, p. ej., en Pascal, un programa se compone de
bloques, un bloque de proposiciones, una proposición de expresiones, una expresión
de componentes léxicos, y éstos de caracteres.
Se puede describir la sintaxis de las construcciones de los lenguajes de
programación por medio de gramáticas independientes de contexto.
Las gramáticas ofrecen ventajas significativas a los diseñadores de
lenguajes y a los escritores de compiladores:
1) Una gramática da una especificación sintáctica y fácil de entender de un
lenguaje de programación.
2) A partir de algunas clases de gramáticas se puede construir automáticamente
un analizador sintáctico eficiente que determine si un programa fuente está
sintácticamente bien formado.
3) Otra ventaja es que el analizador sintáctico puede revelar ambigüedades
sintácticas y otras construcciones difíciles de analizar que de otro modo
podrían pasar sin detectar en la fase inicial de diseño de un lenguaje y de su
compilador.
4) Una gramática diseñada adecuadamente imparte una estructura a un lenguaje
de programación útil para la traducción de programas fuentes a código objeto
correcto y para la detección de errores.
5) Los lenguajes evolucionan con el tiempo, adquiriendo nuevas construcciones y
realizando tareas adicionales. Estas nuevas construcciones se pueden añadir
con más facilidad a un lenguaje cuando existe una aplicación basada en una
descripción gramatical.
El análisis 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, se supone que el analizador sintáctico informará de cualquier error de sintaxis
de manera inteligible. También debería recuperarse de los errores que ocurren
frecuentemente para poder continuar procesando el resto de su entrada.
Componente
léxico
Programa
fuente
Analizador
léxico
Obtén sig.
token
Analizador
Sintáctico
Árbol de
análisis
sintáctico
Resto de la
etapa
inicial
Representación
intermedia
Tabla de
Símbolos
Los métodos empleados generalmente en los compiladores se clasifican
como ascendentes y descendentes.
Los analizadores sintácticos descendentes construyen árboles desde la raíz
hasta las hojas, mientras que los analizadores sintácticos ascendentes comienzan en
las hojas y suben hacia la raíz. En ambos casos examina la entrada al analizador
sintáctico de izquierda a derecha, uno a la vez.
Los métodos ascendente y descendente más eficientes trabajan sólo con
subclases de gramáticas, pero varias de estas subclases, como las gramáticas LL y
LR, son lo suficientemente expresivas para describir la mayoría de las construcciones
sintácticas de los lenguajes de programación.
NOTA: Se asume que la salida de un analizador sintáctico es una
representación del árbol de análisis sintáctico para la cadena de componentes léxicos
producida por el analizador léxico.
Manejo de errores sintácticos
Si un compilador tuviera que procesar sólo programas correctos, su diseño e
implementació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.
La mayoría de las especificaciones de los lenguajes de programación no
describen cómo debe responder un compilador a los errores; la respuesta se deja al
diseñador del compilador.
Considerar desde el principio el manejo de errores puede simplificar la
estructura de un compilador y mejorar su respuesta a los errores.
P. ej., los errores que pueden contener los programas son de diversos tipos:
|
a) Léxicos: como escribir mal un número, un identificador o un operador
b) Sintácticos: como una expresión aritmética con paréntesis no equilibrados
c) Semánticos: como un operador aplicado a un operando incompatible
d) Lógico: como una llamada infinitamente recursiva
Gran parte de la detección y recuperación de errores en un compilador se
centra en la fase de análisis sintáctico. Una razón es que muchos errores son 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.
El manejador de errores en un analizador sintáctico tiene objetivos fáciles de
establecer:
 Debe informar de la presencia de errores con claridad y exactitud
 Se debe recuperar de cada error con la suficiente rapidez como para detectar
errores posteriores
 No debe retrasar de manera significativa el procesamiento de programas
correctos
Estrategias de recuperación de errores
1. En modo de pánico. Al descubrirse un error, el analizador sintáctico desecha
símbolos de la entrada, de uno en uno, hasta que encuentra uno perteneciente a
un conjunto designado de componentes léxicos de sincronización como
delimitadores (punto y coma o pal-res, o “end”, etc)
2. Recuperación a nivel de frase. Al descubrirse un error, el analizador
sintáctico puede realizar una corrección local de la entrada restante; es decir,
puede sustituir un prefijo de la entrada restante por alguna cadena que permita
continuar el análisis sintáctico. (como sustituir una como por un punto y coma,
eliminar un punto y coma sobrante, o insertar uno que falte)
3. Producciones de error. Si se tiene una buena idea de los errores comunes que
pueden encontrarse, se puede aumentar la gramática del lenguaje con
producciones que generen las construcciones erróneas. Se usa entonces esta
gramática aumentada con las producciones de error para construir el analizador
sintáctico. Si el analizador sintáctico usa una producción de error, se pueden
generar diagnósticos de error apropiados para indicar la construcción errónea
reconocida en la entrada.
4. Corrección global. Idealmente, sería deseable que un compilador hiciera el
mínimo de cambios posibles al procesar una cadena de entrada incorrecta y
obtener una corrección global de menor costo (aunque siempre es demasiado
costoso en términos de espacio y tiempo). Se debe señalar que un programa
correcto más parecido al original puede no ser lo que el programador tenía en
mente.
Gramáticas de contexto libre
Muchas construcciones de los lenguajes de programación tienen una estructura
inherentemente recursiva que se puede definir mediante gramáticas independientes
del contexto. P. ej., se puede tener una proposición condicional definida por una regla
como: Si S1 y S2 son proposiciones y E es una expresión, entonces:
“IF E THEN S1 ELSE S2” es una proposición
Por lo tanto:
Proposición  if expresión then proposición else proposición
Una gramática de contexto libre es un cuádruplo G = ( Vt, Vn, So, P)
donde:
Vt, Símbolos terminales
Vn, Símbolos no terminales
So, Símbolo de inicio
P, reglas de producción
P. ej.
expr  expr op expr
expr  ( expr )
expr  - expr
expr  id
a) símbolos terminales : id, +, -, *, /, (, )
b) símbolos no terminales : expr, op
c) Símbolo inicial : expr
Un lenguaje que pueda ser generado por una gramática de contexto libre se dice
que es un lenguaje de contexto libre.
Para comprender cómo trabajan algunos analizadores sintácticos hay que
considerar derivaciones donde tan sólo el no terminal de más a la izquierda de
cualquier forma de frase se sustituya a cada paso. Dichas derivaciones se denominan
por la izquierda.
Convenciones de notación para una gramática:
1) Estos símbolos son terminales: a, b, c, +, -, *, (, ), , , 0, 1, .., 9, id
2) Estos símbolos son no terminales: A, B, C, S, expr, op
3) X, Y, Z representan símbolos gramaticales, es decir terminales o no terminales
4) u, v, w, x, y, z representan cadenas de símbolos terminales
5) las letras griegas representan cadenas de símbolos gramaticales
6) si A , A 2, A n son todas producciones de A,
A 1  2 n
7) el lado izquierdo de la primera producción es el símbolo inicial
E  E A E (E)  E Id
A  + 
DERIVACIONES
Hay varias formas de considerar el proceso mediante el cual una gramática
define un lenguaje.
La idea central es que se considera una producción como una regla de
reescritura, donde el no terminal de la izquierda es sustituido por la cadena del lado
derecho de la producción:
E  E + E E * E  (E) –E Id
Por lo que,
E == > –E
E == > (E)
…
(E deriva –E ó derivación de –E a partir de E)
Árboles de análisis sintáctico y derivaciones:
Un á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:
Escritura de una gramática
Las gramáticas son capaces de describir la mayoría, pero no todas, las sintaxis
de los lenguajes de programación:
Un analizador léxico efectúa una cantidad limitada del analizador sintáctico
conforme produce la secuencia de componentes léxicos a partir de los caracteres de la
entrada. Ciertas limitaciones de la entrada, como el requisito de que los id se declaren
antes de ser utilizados, no pueden describirse mediante una gramática regular. Por lo
tanto, las secuencias de componentes léxicos aceptados por un analizador sintáctico
forman un subconjunto de un lenguaje de programación; las fases posteriores deben
analizar la salida del analizador sintáctico para garantizar la obediencia a reglas que
el analizador sintáctico no comprueba.
Expresiones Regulares VS Gramáticas de Contexto Libre
Toda construcción que se deba describir mediante una expresión regular,
también se puede describir mediante una GCL, P. ej.: la expresión regular (a b)*abb
describe el mismo lenguaje que la GCL:
A0  a A0  b A0  a A1
A1  b A2
A2  b A3
A3  
Las expresiones regulares son muy útiles para describir la estructura de las
construcciones léxicas como identificadores, constantes, números, pal-res, etc
Las GCL, por otra parte, son muy útiles para describir estructuras anidadas,
como paréntesis equilibrados, concordancia de las pal-res como BEGIN-END, los
correspondientes IF-THEN-ELSE, etc. Por lo que estas estructuras anidadas no se
pueden describir con expresiones regulares.
Comprobación del lenguaje generado por una gramática
Así, un conjunto dado de producciones genera un lenguaje determinado. Las<
construcciones problemáticas se pueden estudiar escribiendo una gramática abstracta
concisa y estudiando el lenguaje que genera.
Una prueba de que una gramática G genera un lenguaje L tiene 2 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.
Ambigüedad
Se dice que una gramática que produce más de un árbol de análisis sintáctico
para alguna cadena es ambigua.
Por lo tanto, una gramática ambigua es la que produce más de una derivación
por la izquierda o por la derecha para la misma frase (cadena).
Para algunos tipos de analizadores sintácticos es preferible que una gramática
no sea ambigua, ya que no se podría determinar de manera exclusiva qué árbol de
análisis sintáctico seleccionar para una cadena.
Supresión de la ambigüedad
A veces, una gramática ambigua se puede rescribir para eliminar la
ambigüedad. P. eje. Se eliminará la ambigüedad de la siguiente gramática con
“ELSE” ambiguo:
Prop IF exp. THEN prop  IF exp. THEN prop ELSE prop
p. ej.
IF E1 THEN S1 ELSE IF E2 THEN S2 ELSE S3
Uso de gramáticas ambiguas:
Es un teorema que toda gramática ambigua no es LR, por lo que no está en
ninguna de las clases de gramáticas estudiadas.
Ciertos tipos de gramáticas ambiguas son útiles en la especificación e
implantación del lenguaje.
Para construcciones de lenguajes como las expresiones, una gramática ambigua
proporciona una especificación más natural y corta que cualquier gramática no
ambigua equivalente.
Otro uso de las gramáticas ambiguas está en el aislamiento de construcciones
sintácticas habituales para la optimización en casos especiales.
Con una gramática ambigua se pueden especificar las construcciones de casos
especiales añadiendo cuidadosamente nuevas producciones a la gramática.
Se debe insistir en que, aunque las gramáticas utilizadas son ambiguas, en
todos los casos se especifican reglas para eliminar ambigüedades que permiten sólo
un árbol de análisis sintáctico para cada cadena o frase. De esta manera, la
especificación total del lenguaje sigue siendo no ambigua.
Hay que señalar también que las construcciones ambiguas se deben usar
raramente y de una manera estrictamente controlada, de lo contrario, no se puede
conocer con seguridad el lenguaje que reconoce el analizador.
METODOS DE ANALISIS SINTACTICO
(Segunda parte)
METODOS DE ANALISIS SINTACTICO DESCENDENTE
Introducción:
1. predictivo (pág 44-47)
2. recursividad por la izquierda (pág 47-48)
3. factorización por la izquierda (pág 182-183)
Métodos:
4. por descenso recursivo (pág 186-187)
5. predictivo (pág 187-190)
6. predictivo no recursivo (pág 190-193)
7. Primero y Siguiente (193-200)
METODOS DE ANALISIS SINTACTICO ASCENDENTE
1. por desplazamiento y reducción (incluye mangos y poda pág
200208)
2. por precedencia de operadores (pág 209-221)
3. LR  L: por el examen de la entrada de izq a der (left-toright)
R: por construir una derivación por la derecha
(pág 221-227)
4. SLR (pág 227-236)
5. LR canónico (pág 236-242)
6.
LALR (pág 242-251)
Descargar