.................................................... Ошибка! Закладка не определена. 5.- análisis sintáctico ............................................................................................ 2 5.1 definición y clasificación de gramáticas ....................................................... 2 ..................................................................... Ошибка! Закладка не определена. 5.3 árboles de derivación...................................................................................... 4 5.5 diagramas de sintaxis ..................................................................................... 5 5.6 eliminación de la ambigüedad ..................................................................... 12 5.7 tipos de analizadores sintácticos ................................................................ 13 5.- análisis sintáctico Examen 30 Portafolio 20 Manual de prácticas 30 Propuesta de proyecto 20 100 Competencia especifica Construye un analizador sintáctico a partir de un lenguaje de programación 5.1 definición y clasificación de gramáticas Gramática: La gramática es un ente formal para especificar de una manera finita, el conjunto de cadenas de símbolos que constituyen un lenguaje. Una gramática es una cuádrupla: G= (VT, VN, S, P) Donde: VT= {conjunto finito de símbolos terminales} VN= {conjunto finito de símbolos no terminales} S= es el símbolo inicial y pertenece a VN P= {conjunto de producciones o de reglas de derivación} Todas las cadenas de lenguaje definido por la gramática están formadas con símbolos del vocabulario terminal VT. El vocabulario terminal se define por enumeración de los símbolos terminales Tarea: jerarquía de Chomsky 5.3 árboles de derivación Árbol de derivación: Representación gráfica de cómo se deriva una forma sentencial a partir del símbolo noterminal inicial. Un árbol es un grafo dirigido a cíclico en el cual cada nodo se conecta con un nodo distinguido, llamado nodo raíz mediante un único camino. Un nodo N1 se dice descendiente de otro nodo N2 si se puede llegar a N1 a partir de N1 el nodo raíz no es descendiente de ningún nodo, y los nodos que no tienen descendientes denominan hojas el resto de los nodos se denominan nodos interiores. Nodo raíz Nodos internos Hojas Un árbol de derivación tiene las siguientes propiedades: 1.- el nodo raíz esta rotulado como el símbolo distinguido como el símbolo distinguido (inicial) de la gramática. 2.- cada hoja corresponde a un símbolo terminal o un símbolo no- terminal 3.- cada nodo interior corresponde a un símbolo no- terminal. 5.5 diagramas de sintaxis Son una forma de representar una gramática libre de contexto. Representan una alternativa grafica para la forma de Backus- naur (BNF, por sus siglas en inglés) o la forma extendida de Backus – nar (EBNF). Consta de una serie de cajas o símbolos geométricos conectados por arcos dirigidos. 1- Cada símbolo terminal se representa por su nombre encerrado en un círculo o en una caja de bordes circulares terminal 2- Cada símbolo No terminal se encierra en un rectángulo No terminal 3- Para producciones que tengan varias alternativas el grafo resultante será del tipo: Alternativa Alternativa Alternativa 4- Producciones que contengan una serie concatenada de símbolos terminales y/o no terminales constante . . constante 5- Producciones que tengan cero, una o más repeticiones de un símbolo digito Sentencia condicional, usando diagramas sintácticos letra letra letra if condición then else sentencia sentencia Con grafos o diagrama sintácticas somos capaces de representar la sintaxis de un lenguaje y podemos utilizarlos en lugar del conjunto de producciones en BNF Tarea: gramáticas libres del contexto Ejemplos de diagramas de sintaxis: Gramáticas: E: = E + T| E – T| E E E : = E + T | T E : = E + T | TE E– :T =| E + TE | F E :E =– T E +| T | E FE E– :T =| E + TE | n E E: (–= ETE :| + =T E| E + T T E : = E + T | T: = T * F| T / F| F F: = (E) | n FE E– : T =| E +E T E| E : E =– ET +| T + E :E = : E = + E T |+ T | E * – E E T :/ |– =E : T E | += E E T |+ E T ) | EE : – =T E E |– + T T | Los diagramas de transición son diagramas que representan de forma gráfica las restricciones de una gramática libre de contexto. Diagrama para la instrucción de una asignación: variable expresión = E : = E + T | Diagrama para la instrucción de una condición:E – T | Expresión si ( numérica E: E E = : Final si E = + E T| + Insti. T | E – T| E – T E | E ) E : = E + T | E – T | E entonces Si no Insti. 5.6 eliminación de la ambigüedad Las gramáticas se utilizan para proporcionar la estructura de los archivos, de los programas y documentos. Sin embargo, no todas las gramáticas proporcionan estructuras unidas. Cuando una gramática en proporcionar estructuras inicia a veces es posible rediseñar la gramática para hacer que la estructura sea única para cada cadena. Lamentablemente hay ocasiones en las que esto no es posible Ejemplo E E E + E E * E E E * + E E 1. E=> e+e=> e+e*e 2. E=> e*e=> e+e*e La derivación 1 tiene una segunda “E” que es remplazada por E*E. mientras que en la derivación 2 la primera E es remplazada por E+E, esto genera 2 árboles de derivación diferentes es decir la primera derivación sugiere que 1+(2*3) = 7 se agrupa a si mientras que la segunda derivación dice que la expresión se agrupa así (1+2)*3=9. Por otro lado, la existencia de diferentes derivaciones para una cadena no implica que la gramática sea defectuosa, por ejemplo. 1. E=> E+E=>1 + E=> a+1 => a+b 2. E=> E+E=>1 + 1=> 1+b => a+b Sin embargo, no existe ninguna diferencia real entre las estructuras proporcionadas en estas derivaciones ya que ambas especifican que ay b son identificadores y sus valores deben sumarse. De hecho, ambas derivaciones dan como resultado el mismo árbol. E E E + E E + E I E * E I E * A I I A I I A A A A E Árboles que dan por resultado a+a*a, lo que demuestra la ambigüedad de la gramática de expresiones 5.7 tipos de analizadores sintácticos Ascendente Se parte de las hojas y se intenta construir el árbol hacia arriba hasta llegar al símbolo inicial de la gramática E E E E E id + id + id 5.8 Generación de matriz predictiva (cálculo first y follow) FIRST: Si α es cualquier cadena de símbolos gramaticales, se considera FIRST (α) como el conjunto de terminales que encabezan las cadenas derivadas de α. Si α = * => λ, entonces λ también está en FIRST (α). Para calcular FIRST(X) para algún símbolo X de la gramática, se aplican las siguientes reglas hasta que no se pueda añadir nada nuevo al conjunto FIRST: Sea G:= (V; ∑; Q0; P) una gramática libre de contexto. Para cada forma sentencia α Є (V U ∑)* y para cada k Є N definiremos la función. En otras palabras, el operador FIRST k asocia a cada forma sentencia los primeros k símbolos de cualquier forma terminal alcanzable desde α mediante derivaciones “masa a izquierda". 1. Si X es terminal, entonces FIRST(X) es {X}. 2. Si X es no terminal y existe la producción X → λ, entonces añadir λ a FIRST(X). 3. Si X es no terminal y X → Y1 Y2... . Yk Es una producción entonces, para todo i (con i variando desde 1 hasta k) tal que Y1 , Y2 , ..., Yi-1 sean todos no terminales y FIRST(Y1), FIRST(Y2), ..., FIRST(Yi-1) contengan todos λ, se añaden todos los símbolos no nulos de FIRST(Yi ) a FIRST(X). Finalmente, si λ está en FIRST (Yj) para j = 1, 2, ..., k (o sea, en todos), entonces se añade λ a FIRST(X). Dicho de otra forma, lo anterior significa que todos los elementos de FIRST (Y1), excepto λ, pertenecen también a FIRST(X). Si Y1 no deriva λ, entonces ya ha terminado el cálculo de FIRST(X), pero en caso contrario, es decir, si Y1= * => λ, entonces todos los elementos de FIRST (Y2) excepto λ pertenecen también a FIRST(X), y así sucesivamente. Finalmente, si todos los Yi derivan λ, entonces λ se añade a FIRST(X). FOLLOW: Se define FOLLOW(A), para él no terminal A, como el conjunto de terminales a que pueden aparecer inmediatamente a la derecha de A en alguna forma sentencia, es decir, el conjunto de terminales a tal que haya una derivación de la forma S= * =>αAaβ para algún α y β. Si A puede ser el símbolo de más a la derecha en alguna forma sentencia, entonces $ está en FOLLOW(A). Para calcular FOLLOW(A) para un símbolo no terminal A, se aplican las siguientes reglas hasta que no se pueda añadir nada más al conjunto FOLLOW. 1. $ está en FOLLOW(S), siendo S el axioma de G. 2. Si existe una producción A → αBβ, entonces todo lo que esté en FIRST (β), excepto λ, está en FOLLOW (B). 3. Si existe la producción A → αBβ y FIRST (β) contiene λ (es decir, β= * =>λ), o bien si existe una producción A → αB, entonces todo lo que esté en FOLLOW(A) está en FOLLOW (B). Con las mismas notaciones anteriores, para cada forma sentencia α Є (V U ∑)* definiremos la función FOLLOWG GK (α) del modo siguiente. 5.9 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. Las primeras versiones de los programas suelen ser incorrectas, 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. De corrección: cuando el programa no hace lo que el programador realmente deseaba. Errores de corrección no pueden ser detectados por un compilador, ya que en ellos interviene el concepto abstracto que el programador tiene sobre el programa que construye, lo cual es desconocido, y probablemente incognoscible, por el compilador. La detección de errores lógicos implica un esfuerzo computacional muy grande en tanto que el compilador debe ser capaz de averiguar los distintos flujos que puede seguir un programa en ejecución lo cual, en muchos casos, no sólo es costoso, sino también imposible. Los compiladores actuales se centran en el reconocimiento de los tres primeros tipos de errores. Los errores de sintaxis, que son los que pueden impedir la correcta construcción de un árbol sintáctico. 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, no cancele definitivamente la compilación, sino que se recupere y siga buscando errores. Recuperar un error no quiere decir corregirlo, sino ser capaz de seguir construyendo el árbol sintáctico a pesar de los errores encontrados. El manejador de errores de un analizador sintáctico debe tener como objetivos: Indicar los errores de forma clara y precisa. Debe informar mediante los correspondientes mensajes del tipo de error y su localización Recuperarse del error, para poder seguir examinando la entrada Distinguir entre errores y advertencias. Las advertencias se suelen utilizar para informar sobre sentencias válidas pero que, por ser poco frecuentes, pueden constituir una fuente de errores lógicos No ralentizar significativamente la compilación. Un buen compilador debe conocer los errores que se pueden producir, con lo que se consigue simplificar su estructura. Si el propio compilador está preparado para admitir incluso los errores más frecuentes, entonces se puede mejorar la respuesta ante esos errores incluso corrigiéndolos. 5.10 Generadores de analizadores sintácticos YACC Yacc convierte una gramática independiente del contexto y el código traducido en un conjunto de tablas para un LR (1) analizador y traductor. La gramática puede ser ambigua; reglas de prioridad especificados se utilizan para romper las ambigüedades. El archivo de salida, y.tab.c, debe ser compilado por el compilador C para producir un programa de yyparse. Este programa debe ser cargado con una función del Analizador Léxico, yylex (void) (a menudo generada por la lex (1)), con un main (int argc, char * argv []) del programa, y con la rutina de manejo de errores, yyerror (char *).