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 sintácticos: Yacc, Bison
para
analizadores
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+)?
(.digito+)?
• Eb delim+ • Delim blanco | tab | linenueva
(E(+|-)?
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 metasintaxis; 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> <namepart> <personal-part> <initial> "."
::=
<first-name>
|
BNF
• <street-address> <street-name> <EOL>
::=
<house-num> <opt-apt-num>
• <zip-part> ::= <town-name> <state-code> <ZIP-code> <EOL> • <opt-jr-part> ::= "Sr." <roman-numeral> | ""
|
"Jr."
"," |
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 terminales en minúsculas.
símbolos
no
• Cada regla de producción termina con el símbolo de punto y com (;). • El operador | indica una alternativa de regla de producción.
EBNF
digito 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: *)
EBNF
natural = 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
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ánico
int 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 COMPILERCOMPILER): 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 yylineno); • }
en
%dlinea”,
• 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.
CUP
import java_cup.runtime.SymbolFactory; %% %cup %class Scanner %{ public Scanner(java.io.InputStream SymbolFactory sf){ this(r); this.sf=sf; }
r,
CUP
private SymbolFactory sf; %} %eofval{ return sf.newSymbol("EOF",sym.EOF); %eofval} %% ";" { return sf.newSymbol("Semicolon",sym.SEMI); } "+" { return sf.newSymbol("Plus",sym.PLUS); } "*" { return
CUP
sf.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
CUP
import 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).par se();
• } • :}
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;
CUP
expr_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; :} ;
• Escrito: 70%
Examen
• 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
â&#x20AC;˘ 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. AddisonWesley Iberoamericana. • Kenneth C. Louden. Construcción de compiladores Principios y práctica. Ed. Thomson.
Referencias
â&#x20AC;˘ EBNF, Wikipedia la Enciclopedia Libre, http://www.wikipedia.org/ Recuperado en octubre de 2007.
多Preguntas?