Agenda Introducción Analizador léxico Analysis El problema de analizar sintácticamente Analizador sintáctico descendeterecursivo Analizador sintáctico Bottom-Up 1-1
1-2
Introducción Los sistemas de implementación de lenguajes deben analizar código fuente, independientemente del enfoque de implementación específica Casi todos los analizadores sintácticos se basan en una descripción formal del lenguaje (BNF) 1-3
Análisis sintáctico Casi siempre el análisis sintáctico está compuesto de dos partes: Un componente de bajo nivel llamado analizador léxico (matemáticamente, un autómata finito basado en una gramática regular) Un componente de alto nivel llamado analizador sintáctico, o parser (matemáticamente, un autómata de pila basado en una gramática independiente del contexto, o BNF) 1-4
Usar BNF para describir la sintaxis Provee una descripción sintáctica clara y precisa El analizador sintáctico puede estar basado directamente en el BNF Los analizadores sintácticos basados en BNF son fáciles de mantener 1-5
Razones para separar el análisis léxico del análisis sintáctico Simplicidad enfoques menos complejos pueden ser usados para analizar léxicamente; separarlos simplica el parser Eficiencia la separación permite la optimización del analizador léxico Portabilidad partes de un analizador léxico pueden no ser portables pero el parser siempre es portable 1-6
Análisis léxico Un analizador léxico busca coincidencias de patrones en cadenas de caracteres Un analizador léxico es un front-end para el parser Identifica subcadenas del programa fuente - lexemes Los lexemas son cadenas que coinciden con un patrón, el cual está asociado a una categoría conocida como token sum es un lexema; su token podría ser IDENT 1-7
Análisis léxico (continuación) El analizador léxico es usualmente una función que es llamada por el parser cuando éste necesita el siguiente token Existen tres enfoques para construir un analizador léxico: Escribir una descripción formal de los tokens y usar una herramienta computacional que construya una tabla dada su descripción Diseñar un diagrama de estado que describa los tokens y escribir un programa que implemente dicho diagrama Diseñar un diagrama de estados que describa los tokens y construir una tabla manualmente de su descripción 1-8
Diseño del diagrama de estados Un diagrama de estados simple tendría una transición de cada estado de cada caracter en el código fuente - dicho diagrama sería muy largo 1-9
Análisis léxico (cont.) En muchos casos las transiciones pueden ser combinadas para simplificar el diagrama de estados Cuando se reconozca una literal entera, todos los dígitos son equivalentes usar una clase de dígito 1-10
Análisis léxico (cont.) Palabras reservadas e identificadores pueden ser reconocidos juntos (en lugar de tener una parte del diagrama para cada palabra reservada) Usar una tabla de búsqueda para determinar si un identificador es en realidad una palabra reservada. 1-11
Análisis léxico (cont.) Subprogramas utilizados convenientemente: getchar obtiene el siguiente caracter, y lo coloca en nextchar, determina su clase y la coloca en charclass addchar copia el caracter de nextchar en el lugar donde el lexema está siendo acumulado, lexema lookup determina si la cadena en lexeme es una palabra reservada (regresa un código) 1-12
Diagrama de estados 1-13
Análisis léxico (cont.) Implementación: int lex() { getchar(); switch (charclass) { case LETTER: addchar(); getchar(); while (charclass == LETTER charclass == DIGIT) { addchar(); getchar(); } return lookup(lexeme); break; 1-14
Análisis léxico (cont.) case DIGIT: addchar(); getchar(); while (charclass == DIGIT) { addchar(); getchar(); } return INT_LIT; break; } /* End of switch */ } /* End of function lex */ 1-15
El problema de analizar sintácticamente Objetivos del parser dado un programa de entrada: Encontrar todos los errores de sintáxis; de cada cada uno, producir un mensaje de diagnóstico apropiado Producir un árbol de sintáxis 1-16
El problema de analizar sintácticamente (cont.) Hay dos categorías de analizadores Top down produce el árbol de sintáxis comenzando por la raíz Su orden de derivación por la derecha Construye un árbol en preorden Bottom up produce el árbol de sintaxis comenzando por las hojas Sigue un orden de derivación invertida: por la izquierda Los analizadores buscan sólamente un token hacia adelante 1-17
El problema de analizar sintácticamente (cont.) Analizador sintáctico Top-down Dada una forma sentencial, xaα, el analizador debe escoger la A-regla correcta para obtener la siguiente forma sentencial en la derivación por la izquierda, usando sólamente el primer token producido por A Los analizadores síntacticos top-down más comunes: Descendente recursivo Analizadores sintácticos LL (Left to right Leftmost derivation) implementación basada en tablas 1-18
El problema de analizar sintácticamente (cont.) Analizadores Bottom-up Dada una forma sentencial, α, determinar que subcadena de es la parte derecha de la regla en la gramática que debe ser reducida para producir la forma sentencial previa en la derivación derecha El analizador sintáctico bottom-up más común se encuentran en la familia LR (Left to right Right most derivation). 1-19
El problema de analizar sintácticamente (cont.) La complejidad del análisis Los analizadores que trabajan para cualquier gramática no-ambigua son complejos e ineficientes ( O(n 3 ), donde n es la longitud de la entrada ) Los compiladores usan analizadores que sólamente trabajan para un subconjnto de todas las gramáticas no-ambiguas, pero lo hacen en tiempo lineal ( O(n), donde n es la longitud de la entrada ) 1-20
Analizador descendente recursivo Hay un subprograma para cada noterminal que puede analizar las sentencias que pueden ser generadas por dicho no-terminal EBNF es ideal para ser usado por una analizador descendente recursivo porque éste minimiza el número de no-terminales 1-21
Analizador descendente recursivo (cont.) Una gramática para expresiones simples: <expr> <term> {(+ -) <term>} <term> <factor> {(* /) <factor>} <factor> id ( <expr> ) 1-22
Analizador descendente recursivo(cont.) Asumamos que tenemos un analizador léxico llamado lex, el cual copia el siguiente token en nexttoken El proceso de codificación cuando hay sólamente un RHS: Para cada símbolo terminal en el RHS, se compara con el siguiente token; si coinciden, se continua, en caso contrario hay un error Para cada símbolo no-terminal en el RHS, llama a su programa analizador asociado. 1-23
Analizador descendente recursivo (cont.) /* Function expr Parses strings in the language generated by the rule: <expr> <term> {(+ -) <term>} */ void expr() { /* Parse the first term */ term(); 1-24
Analizador descendente recursivo(cont.) /* As long as the next token is + or -, call lex to get the next token, and parse the next term */ while (nexttoken == PLUS_CODE nexttoken == MINUS_CODE){ lex(); term(); } } Esta rutina particular no detecta errores Convención: Cada rutina del analizador deja el próximo token en nexttoken 1-25
Analizador descendente recursivo (cont.) Un no terminal que tiene más de una RHS requiere un proceso inicial para determinar cual RHS está analizando El correcto RHS es escogido con base al siguiente token (el lookahead) El próximo token es comparado con el primer token que puede ser generado por cada RHS hasta que se encuentre una coincidencia Si no se encuentran coincidencias, entonces existe un error 1-26
Analizador descendentes recursivos(cont.) /* Function factor Parses strings in the language generated by the rule: <factor> -> id (<expr>) */ void factor() { /* Determine which RHS */ if (nexttoken) == ID_CODE) /* For the RHS id, just call lex */ lex(); 1-27
Analizador descendente recursivo (cont.) /* If the RHS is (<expr>) call lex to pass over the left parenthesis, call expr, and check for the right parenthesis */ else if (nexttoken == LEFT_PAREN_CODE) { lex(); expr(); if (nexttoken == RIGHT_PAREN_CODE) lex(); else error(); } /* End of else if (nexttoken ==... */ } else error(); /* Neither RHS matches */ 1-28
Analizador descendente recursivo (cont.) La clase gramatical LL El problema de la recursión por la izquierda Si una gramática tiene recursión por la izquierda, ya sea directa o indirectamente, no puede ser la base para un analizador top-down Se puede modificar una gramática para remover la recursión por la izquierda 1-29
Analizador descendente recursivo(cont.) La otra característica de las gramáticas que impide un análisis top-down es la falta de emparejamiento. La inhabilidad para determinar el RHS correcto de acuerdo a la base de un token de lookahead 1-30
Analizador descendente recursivo(cont.) La prueba del emparejamiento: Para cada no terminal, A, en la gramática tiene más de un RHS, para cada par de reglas, A α i y A α j, debe ser verdad que FIRST(α i ) diferente a FIRST(α j ) φ Examples: A a bb cab A a ab (Factorización por la izquierda) 1-31
Analizador Bottom-up El problema de analizar sintáticamente es encontrar la correcta RHS en una forma sentencial derecha para reducir y obtener la forma sentencial derecha previa en la derivación 1-32
Analizador Bottom-up (cont.) Shift-Reduce Algorithms Reduce es la acción de reemplazar el manejador que está en la cima de la pila con su correspondientelhs Desplaza es la accioń de mover el siguiente token a la cima de la pila 1-33
Analizador Bottom-up (cont.) Ventajas de los parsers LR Trabajan con casi cualquier gramática. Además son igual de eficientes que los bottom-up Pueden detectar errores rápidamente. Las clases de gramáticas LR son un superconjunto de las clases de analizables sintácticamente por los parsers LL. 1-34
Analizador Bottom-up (cont.) Los parsers LR deben ser construidos con una herramienta: YACC, BISON, ANTLR, etc 1-35
Analizador Bottom-up (cont.) Una configuración LR guarda el estado de un parser LR, mientras que para construir un LL, se necesita muchísima memoria. (S 0 X 1 S 1 X 2 S 2 X m S m, a i a i +1 a n $) 1-36
Analizador Bottom-up (cont.) Los parsers LR están basados en tablas, donde la tablas tiene dos componentes, una tabla ACCIÓN y una tabla GOTO La tabla ACCIÓN especifica la acción a realizar, a partir de un estado dado y el siguiente token Los renglones son los nombres de estado y las columnas las terminales La tabla GOTO especifica cuál estado será puesto en la cima de la pila después que una reducción sea hecha. Los renglones son los nombres del estado y las columnas los no terminales. 1-37
Estructura de un Analizador LR 1-38
Analizador Bottom-up (cont.) Configuración Inicial: (S 0, a 1 a n $) Acciones del parser: If ACTION[S m, a i ] = Desplaza S, la próxima configuración es: (S 0 X 1 S 1 X 2 S 2 X m S m a i S, a i+1 a n $) If ACTION[S m, a i ] = Reduce A β y S = GOTO[S m-r, A], realizado r = la longitud de β, la próxima configuración es (S 0 X 1 S 1 X 2 S 2 X m- rs m-r AS, a i a i+1 a n $) 1-39
Analizador Bottom-up (cont.) Acciones del parser (cont.): If ACTION[S m, a i ] = Acepta, el análisis está completo y se encontraron errores. If ACTION[S m, a i ] = Error, el parser llama a una rutina para manejar errores. 1-40
Analizador Bottom-up (cont.) Una tabla de un parser puede ser generada desde una herramienta para manejar gramáticas, e.g., yacc 1-41
Resumen La sintaxis es algo usual al momento de implementar lenguajes Un analizador léxico analizador de similitudes que aisla las pequeñas partes de un programa Detecta errores Produce un arbol de análisis. Un parser recursivo descendente es del tipo LL EBNF El problema de analizar sintáticamente parsers bottom-up: encontrar la subcadena de la forma sentenciales actual. La familia LR de parsers shift-reduce son más comunes que los enfoques basados en bottomup. 1-42