Análisis Sintáctico
M.C. Juan Carlos Olivares Rojas
Agenda• Introducción a las Gramáticas libres de
contexto y árboles de derivación.
• Diagramas de sintaxis.
• Precedencia de operadores.
• Analizador sintáctico. – Analizador descendente (LL). – Analizador ascendente(LR, LALR).
Agenda• Administración de tablas de símbolos
• Manejo de errores sintácticos y su recuperación
• Generadores de código para analizadores sintácticos: Yacc, Bison
Introducción a las GICs y Árboles de Derivación
• Todo lenguaje posee una serie de reglas para describir los programas fuentes (sintaxis).
• Un analizador sintáctico implementa estas reglas haciendo uso de GICs (Gramáticas Independientes del Contexto).
Gramáticas• Son un formalismo matemático que
permite decidir si una cadena pertenece a un lenguaje dado.
• Se define como la cuarteta G= (N, Σ, S, P), en donde N es el conjunto de símbolos terminales, Σ es conjunto de símbolos terminales, S es el símbolo inicial (S pertenece a N) y P es un cojuntode reglas de producción.
Gramáticas• Los símbolos no terminales (N) son
aquellos que pueden seguir derivando en otros; mientras que los terminales el proceso finaliza allí.
• Las reglas de producción siguen el formato: αβdonde α y βpertenecen a N y Σen cualquier forma.
Reglas de Producción• Son las reglas que permiten decidir si la
cadena pertenece a un lenguaje y la estructura que lleva:
• SA | aB • Bε • AaA | bC • Cε
• S Genera cadenas del lenguaje a*b u a
Tipos de gramáticas• Las gramáticas más sencillas son las
gramáticas regulares, debido a que no presentan anomalías de ningún tipo. Desafortunadamente este tipo de gramáticas no permiten expresar todos los lenguajes posibles y en especial los humanos por lo que se necesitan otros tipos de gramáticas.
• Las más utilizadas en informáticas son las libres del contexto.
Gramáticas Regulares• Son las que se forman a través de
Autómatas Finitos Deterministas y Expresiones regulares. No presentan ambiguedades.
• Sus reglas de producción son del tipo: αβ donde α pertenece a N y β pertenece a (Σ)* N?
GICs• Son aquellas G cuya reglas de producción
son de la forma: αβ, en donde αpertenece a N y βpertenece (N u Σ)*
• Las ventajas de uso de GICs son: – Proporcionan una estructura sintáctica
precisa y fácil de comprender.– Proporciona al lenguaje fuente una estructura
adecuada para la generación del código.– Por medio de las GICs es fácil construir
analizadores sintácticos.
GICs• Hay que revisar que la gramática no sea
inherentemente ambigua para poder eliminar esa ambigüedad o rediseñar la gramática sin anomalías.
• Algunas formas de eliminar esa ambigüedad es utilizando técnicas como algoritmos CYK y las formas normales de Chomsky(FNCh) y Greibach(FNG).
Ejemplo de GICs• Expresiones válidas en lenguajes C:
• expr (expr) | - expr | expr op expr | VAR | NUM
• Error sintáctico: cuando la secuencia de componentes léxicos no puede ser generada por la gramática del lenguaje fuente.
Ejemplos de GICs• Declaración de variables en C:
• Decl TIPO listavar PYC • listavarvar | var COMA listavar • varID | ASTER var • dimensionCI ENTERO CD | CI ENTERO
CD dimension
Gramática de un if• prop if expr then prop | if expr then
prop else prop | ε
• exprtermino oprel termino | termino
• termino id | num
• oprel < |>|=|<=|<>|>=|
• idletra (letra | digito)*
Gramática de un if• num digito+ (.digito+)? (E(+|-)?
digito+)?
• Eb delim+
• Delim blanco | tab | linenueva
Jerarquía de Chomsky• Las otras dos gramáticas en las cuales
clasificó Chomsky (GR tipo 3, GIC tipo 2) son las gramáticas sensible al contexto (tipo 1, donde |α| < |β|, donde α y β pertenecen a (Σ u N)* salvo ε) y las gramáticas del tipo 0 o sin restricciones, las cuales sus reglas de producción pueden ser de cualquier tipo.
Árboles de Derivación• Es la representación gráfica de la
derivación de una cadena.
• Se crea utilizando el símbolo inicial como la raíz, los símbolos N representan nodos del árbol y los símbolos Σ las hojas del árbol.
• A través de los árboles de derivación se puede verificar la sintaxis de un lenguaje así como comprobar el significado de las palabras.
Árboles de Derivación• Si para la misma cadena existen dos o
más árboles de derivación la gramática es ambigua.
BNF• La Forma Backus-Naur es una meta-
sintaxis; es decir, una sintaxis para representar sintaxis.
• Es un estándar para representar lenguajes.
• Los paréntesis triangulares < y > sirven para indicar los símbolos no terminales.
• La barra vertical | para representar Ó
BNF• La doble flecha indica las derivaciones
• ::= indica las producciones
• [] indican elementos opcionales
• {} indican términos repetitivos
BNF<postal-address> ::= <name-part>
<street-address> <zip-part>
<name-part> ::= <personal-part> <last-name> <opt-jr-part> <EOL> | <personal-part> <name-part>
<personal-part> ::= <first-name> | <initial> "."
BNF• <street-address> ::= <house-num>
<street-name> <opt-apt-num> <EOL>
• <zip-part> ::= <town-name> "," <state-code> <ZIP-code> <EOL>
• <opt-jr-part> ::= "Sr." | "Jr." | <roman-numeral> | ""
EBNF• Extended Backus Naur Form es una
metasintaxis ampliamente utilizada que mejora a su antecesor BNF.
• Ha cambiado la forma de realizar la especificación de las reglas de producción de la gramática.
• La motivación para usar EBNF radica que con BNF los elementos repetitivos necesitan de más reglas de producción para trabajar.
EBNF• Las reglas de producción pueden contener
espacios.
• Los símbolos terminales se representan con comillas dobles (“”) cuando representan un símbolo del alfabeto y comillas simples (‘’) para representar cadenas
• El operador de producción ahora es el símbolo de igual (=)
EBNF• Se recomienda escribir los símbolos no
terminales en minúsculas.
• Cada regla de producción termina con el símbolo de punto y com (;).
• El operador | indica una alternativa de regla de producción.
EBNFdigito sin cero = “1” | “2” | “3” | “4” | “5” | “6” |
“7” | “8” | “9”;digito = “0” | digito sin cero
• Las comas (,) sirven para separar tanto terminales como no terminales de las reglas de producción.
• Las llaves ({}) indican elementos repetitivos (operador estrella: *)
EBNFnatural = digito sin cero, {digito};
• Los corchetes ([]) se manejan para elementos auxiliares.
• entero = “0” | [“-”], natural
• Entre símbolos de interrogación (?) se pueden poner símbolos especiales.
EBNF• Un espacio en blanco se define como:
espacio = ? US-ASCII character 32 ?;
• Se pueden poner comentarios con los símbolos (* comentario *)
• Los paréntesis “(” y “)” se utilizan para agrupar símbolos. El símbolo “-” sirve para expresar excepciones.
EBNF• Se utiliza “*” para indicar repeticion, por
ejemplo
• regla = “A”;• repetición = 3 * aa, “B”;
• Si se deriva la regla de producción repetición la cadena generada sería: AAAB
EBNF• Se pueden anidar operadores como *, {}
y [] para lograr cualquier tipo de repetición.
• Tanto BNF como EBNF pueden determinar cualquier tipo de gramática, sencillamente EBNF permite simplificar y tener menos ambigüedad en la metasintaxis.
Diagramas de Sintaxis• Es otra forma (al igual que los árboles de
derivación) de especificar gramáticas de cualquier tipo en especial de tipo 2.
• La característica de este esquema es que permite ver las derivaciones al instante de que ocurren.
• Es una forma visual de representar la gramática de un lenguaje.
Diagramas de Sintaxis
Precedencia de Operadores• La precedencia de operadores es de vital
importancia en el proceso de análisis sintáctico ya que nos representará la forma en que debe construirse el árbol de derivación.
• En aritmética existen prioridades, por ejemplo: * y / tienen preferencia sobre + y -. () indican la máxima prioridad.
Precedencia de Operadores• La instrucción a = b + c / 2 en la mayoría
de los lenguajes no se evalúa de la forma a = (b + c) /2, sino de la forma a = b + (c/2)
• La forma de evaluación depende de cómo se construyan los operadores, ya sea en infijo, postfijo o prefijo.
• Las operaciones se realizan de abajo hacia arriba.
Precedencia de Operadores• Podría pensarse que esta parte
corresponde a la parte semántica dado que el resultado de las operaciones depende del orden. Al hablar de orden se hace referencia al aspecto sintáctico y gramatical.
• Las reglas gramaticales de mayor prioridad deben de definirse primero.
• ¿Cómo se diferencia el operador “-” binario del unario?
Tarea• UML es el lenguaje de modelado
unificado. Tiene la característica principal de ser visual y extensible.
• Al ser un lenguaje formal debe de contener una estructura bien definida, en este caso vocabulario (alfabeto) bien definido y reglas gramaticales para definir el orden o sintaxis de los elementos así como una semántica o significado propio.
Tarea• Realizar un trabajo de investigación que
contenga la definición de una gramática visual y sus características.
• Además, se debe de investigar de un diagrama UML en particular (versión 2.0 o superior) los elementos de léxico, sintaxis y semántica.
• Se deberá preseleccionar (orden en que lleguen los correos) el tipo de diagrama.
Tarea• La tarea se entregará el martes 3 de
noviembre.
• La tarea es individual.
• Se recomienda consultar libros de UML sólo se deberá tener cuidado de encontrar como la sintaxis y semántica del documento (se puede utilizar una herramienta de diagramación pero quizás no cumpla con el estándar).
Analizador Sintáctico• Un analizador sintáctico (Parser) es un
programa que reconoce si una o varias cadenas de caracteres forman parte de un determinado lenguaje.
• Los lenguajes habitualmente reconocidos por los analizadores sintácticos son los lenguajes libres de contexto.
Analizador Sintáctico• Los analizadores sintácticos fueron
extensivamente estudiados durante la década de 1970, detectándose numerosos patrones de funcionamiento en ellos, cosa que permitió la creación de programas generadores de analizadores sintácticos a partir de una especificación de la sintaxis del lenguaje, tales como YACC, GNU bison y javacc.
Análisis Sintáctico• Es el proceso de determinar si una cadena dada puede ser generada por una gramática.
• Los analizadores sintácticos de lenguajes de programación suele hacerse de izquierda a derecha, viendo un componente léxico a la vez.
Análisis Sintáctico
Análisis Sintáctico• Los analizadores pueden clasificarse
dependiendo de la forma en como se construyen los nodos del árbol de derivación sintáctico: ascendentes y descendentes.
• El análisis sintáctico impone una estructura jerárquica.
• Sea la expresion: id1 := id2 + id3 * 60
Análisis Sintáctico• Dicha expresión queda expresada en un
árbol sintáctico de la siguiente forma:
:= id1 + id2 * id3 60
Tipos de Analizadores Sintácticos
• LL (left toleft) leen la cadena de izquierda a derecha y derivan por la izquierda
• LR (lefttoright)
• SaA • AaBbC • Bb • Cc
Analizador Descendente• Existen diferentes métodos de análisis
sintáctico. La mayoría caen en una de dos categorías: ascendentes y descendentes. Los ascendentes construyen el árbol desde las hojas hacia la raíz. Los descendentes lo hacen en modo inverso.
• Un analizador ampliamente utilizado es el llamado de análisis predictivo descendente recursivo que es muy sencillo.
Analizador Descendente• Derivación izquierda:
• SAaaBbCaabbCaabbc(1234) • SaAaaBbaaBbcaabbc(3421)
• LL(k) traductores “top-down”
• Un análisis anticipado de k caracteres (predictivo de caracteres)
Analizador Descendente• SaS | cA • AbA | cB | ε • BcB |a | ε
• Construir la cadena acbb:
• SaS o ScA; al anticipar el primer símbolo.
Analizador Descendente
Administración de la Tabla de Símbolos
• La tabla de símbolos se crea durante la fase de análisis léxico a través de los componentes léxicos, pero en el proceso de análisis sintáctico sufren algunas modificaciones.
• Generalmente se agregan valores de tipo y significado para el análisis sintáctico.
Manejo de errores sintácticos y su recuperación
• Si los traductores tuvieran que procesar programas correctas el proceso de implantación se simplificaría mucho.
• ¿Cómo debe de responder un compilador de pascal a un código Fortran?
• Ningún método de recuperación de errores resuelve todos los problemas
Tipos de Errores• 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ógicos: como una llamada infinitamente recursiva.
Tipos de Errores• La mayoría de los errores se centra en la
fase de análisis sintáctico.
• El manejador de errores debe:
• Informar la presencia de errores con claridad y exactitud.
• Brindar una posible solución al problema.
Administrador de Errores• Recuperar de cada error con la suficiente
rapidez como para detectar errores posibles.
• No debe retrasar de manera significativa el procesamiento de programas correctos.
• Debe indicar la línea del error y algún mensaje informativo
Estrategia de Recuperación de Errores
• Modo Pánico
• Nivel de Frase
• Producciones de error
• Corrección global
Recuperación en Modo Pánico• Es el más sencillo de implantar.
• El analizador sintáctico desecha componentes léxicos hasta encontrar un carácter de sincronización. Estos caracteres son el punto y como (;) entre otros.
• En C/Java es muy común este tipo de errores.
Recuperación en Modo Pánicoint a.b,c; struct c { …. }
main() { int a; }
Recuperación a nivel de frase• Esta técnica utiliza una corrección de
caracteres adyacentes, ya sea por inserción, eliminación o intercambio.
• Esta técnica permite sustituir “,” por “;”, etc. Son traductores que corrigen errores.
• Desafortunadamente para muchos casos no aplican por lo que no se utilizan demasiados.
Producción de Errores• Se pueden generar gramáticas para
generar producciones de error y así de esta forma seguir con el proceso.
• La dificultad radica en el sentido de encontrar esas reglas gramaticales para generar error. En algunos casos sería inclusiva más extensa que la gramática del propio lenguaje.
• for(i<3, a<10; i++)
Corrección Global• Idealmente, sería recomendable que un
traductor hiciera el mínimo de cambios para procesar una entrada inválida. Este algoritmo genera menores costos globales para realizar cambios.
• El problema radica en que el implementar estas estrategias son muy costosas en tiempo y espacio.
Generadores de Analizadores Sintácticos: YACC & Bison
• YACC (YET ANOTHER COMPILER- COMPILER): es una herramienta que nos permite validar lenguajes a través de la especificación de reglas gramaticales.
• Esta fuertemente relacionado con Lex para la identificación de caracteres extraños.
YACC• La forma de trabajar con yacc es la
siguiente:
• Analizador.y (#include“lex.yy.c”) bison analizador.c (y.tab.c) gcc analizador
• $gcc analizador.c –o analizador –lfl
• Se necesita modificar el valor de retorno en el analizador léxico para trabajar de manera conjunta.
Estructura de un programa en YACC/BISON
%{ Declaraciones globales C }% Declaraciones bison
%% Gramáticas Nombre:prod1|prod2|…|prodn;
%% Código auxiliar C
Consejos• Todo lexema debe ser un entero: • #define VAR 200 (256) • return (VAR);
• Gramática vacía: prod1| prod2| ;
• Es sumamente sensible a los separadores.
Consejos• Yacc no es una herramienta que se
caracterice por optimizar gramáticas. Por lo tanto, se pueden presentar algunos problemas de ambigüedad:
• Reduce/Reduceambigüedad infinita • Shift/Reduce
• A continuación se muestran algunos ejemplos de Yacc.
Analizador.lex%{ #include“ytab.h” }% sp [\n\r\t] if [i][f] %% {if} {return(IF);} “(” {return(PI); } . return(ERROR); %%
ytab.h• /*Se deben colocar todos los tokens*/• #define IF 1• #define
Analizador.y• %{ • #include“lex.yy.c” • }% • %token IF PI PD LLI LLD • %token ID NUM OPREL OPLOG • %% • programa: linea programa | ;• Linea: iflinea | ; • if: ifPI condicionPD LLI campo LLD ;
Analizador.y• .: {printf(“Error sintáctico”);}
• %% • main(intargc, char*argv[]) { • FILE *f = fopen(argv[1], “r”); • yyin= f; • while(yyparse()); • fclose(f); }
Compilación con Yacc• $flexanalizador.lex • $bisonanalizador.y • $gccanalizador.c –o analizador –lfl • yytextcomponente léxico • yyinflujo de entrada • yylinenolínea de error
Manejo de errores con Yacc• %% yyerror() { • printf(“Error sintáctico en %dlinea”,
yylineno); • }
• En general, el manejo de errores se realiza de forma trivial. De lo contrario tendría que ser más específico.
CUP• Es el generador de analizadores sintácticos
más populares para Java.
• Al igual que con lex, los archivos de JLEX deben de modificarse para poder trabajar en conjunto.
• Se deben devolver valores de la clase sym, además de incluir la biblioteca de cup. A continuación se muestra un ejemplo de un analizador sintáctico de expresiones.
CUPimport java_cup.runtime.SymbolFactory;%%%cup%class Scanner%{
public Scanner(java.io.InputStream r, SymbolFactory sf){
this(r);this.sf=sf;
}
CUPprivate SymbolFactory sf;%}%eofval{ return sf.newSymbol("EOF",sym.EOF);%eofval}%%";" { return
sf.newSymbol("Semicolon",sym.SEMI); }"+" { return
sf.newSymbol("Plus",sym.PLUS); }"*" { return
CUPsf.newSymbol("Times",sym.TIMES); }"(" { return sf.newSymbol("Left
Bracket",sym.LPAREN); }")" { return sf.newSymbol("Right
Bracket",sym.RPAREN); }[0-9]+ { return sf.newSymbol("Integral
Number",sym.NUMBER, new Integer(yytext())); }
[ \t\r\n\f] { /* ignore white space. */ }. { System.err.println("Illegal character:
"+yytext()); }
CUP• En cup se definen las gramáticas en una
sintaxis muy similar a BNF.
• El archivo define algunas secciones para código auxiliar y definición de terminales y no terminales.
• Para compilar se debe de incluir en el path la ruta de la biblioteca de cup. El archivo generado por cup se denomina parser.java y sym.java que se compilan con el lexer.java
CUPimport java_cup.runtime.*;parser code {:
public static void main(String args[]) throws Exception {
SymbolFactory sf = new DefaultSymbolFactory();
if (args.length==0) new parser(new Scanner(System.in,sf),sf).parse();
else new parser(new Scanner(new java.io.FileInputStream(args[0]),sf),sf).parse();
CUP• }• :}
• terminal SEMI, PLUS, TIMES, LPAREN, RPAREN;
• terminal Integer NUMBER;• non terminal expr_list, expr_part;• non terminal Integer expr;• precedence left PLUS;• precedence left TIMES;
CUPexpr_list ::= expr_list expr_part | expr_part;expr_part ::= expr:e {: System.out.println("
= "+e+";"); :} SEMI;expr ::= NUMBER:n
{: RESULT=n; :} | expr:l PLUS expr:r
{: RESULT=new Integer(l.intValue() + r.intValue()); :} | expr:l TIMES expr:r {: RESULT=new Integer(l.intValue()
CUP* r.intValue()); :}
| LPAREN expr:e RPAREN {: RESULT=e; :} ;
Examen• Escrito: 70%
• Próxima unidad se juntará análisis sintáctico y semántico del ensamblador de Java.
• Etiquetas para unidad 5: aconst_null, aload, bipush, breakpoint, cmpgd, cmpldconst, iadd, iaload, iand, iastore, iconst, idiv, if_acmpeq, if_acmpne, if_icmpeq, if_icmpge, if_icmpgt, if_icmple,
Examen• if_icmplt, if_icmpne, ifeq, ifge, ifgt, ifle,
iflt, ifne, ifnonnull, ifnull, iinc, iload, iload_<n>, imul, ineg, ishl, ishr, istore, istore_<n>, isub, iushr, ixor, jsr, jsr_w.
Pendientes• Práctica 9: viernes programa para
reconocer if en yacc/cup.
Referencias• Aho, Sethi, Ullman. Compiladores
Principios, técnicas y herramientas Ed. Addison Wesley.
• Beck,. Software de Sistemas, Introducción a la programación de Sistemas Ed. Addison-Wesley Iberoamericana.
• Kenneth C. Louden. Construcción de compiladores Principios y práctica. Ed. Thomson.
Referencias• EBNF, Wikipedia la Enciclopedia Libre,
http://www.wikipedia.org/ Recuperado en octubre de 2007.
¿Preguntas?
Recommended