Facultad de Ingeniería de Sistemas Lenguajes y Compiladores Introducción Compiladores 1 Objetivos Conocer los fundamentos de construcción de Compiladores en todas sus fases, presentando los conceptos básicos, definiciones formales, técnicas utilizadas, las clases de compiladores, el contexto en el que se desarrollan, así como el tratamiento y recuperación de errores Aplicar estos conceptos al desarrollo de: Software de base Software de uso específico Interfaces de usuario Compiladores 2 Introducción ¿Por qué es importante el estudio de compiladares? Conocer el diseño de los compiladores y su efecto sobre los lenguajes: Permite desarrollar algoritmos eficientes: por ejemplo cuando usar recursión. Mejora el uso del lenguaje disponible: si el uso de apuntadores es eficiente o no. Ayuda a la depuración de los errores tanto sintácticos como semánticos Compiladores 3 Introducción En 1940 surge el 1° lenguaje utilizado en la programación de computadoras : “El lenguaje de máquina” (lenguaje de 1era generación). ¿Cuál era ventaja de programar en lenguaje de máquina ? El lenguaje de máquina, es el único lenguaje que la computadora entiende directamente. ¿Cuáles eran los inconvenientes o desventajas? Dificultad y lentitud en la codificación. Gran dificultad para verificar y poner a punto los programas. Compiladores 4 Introducción > Las desventajas Las ventajas. Surge la necesidad de buscar otros lenguajes. ¿ Que sucedió a comienzos de la década de 1950 ? Criterio : Usar notación simbólica. ¿Cómo se llamó a esos lenguajes? Fue el primer intento. Esos lenguajes fueron de alto o bajo nivel? ¿Esos lenguajes presentaron la mayoría de inconvenientes que presentaba el lenguaje de máquina?. Compiladores 5 Introducción La interface (de programación) entre los humanos y las máquinas son los Lenguajes de programación: La solución a un problema se especifica a través de un programa fuente escrito en un Lenguaje de programación. Es necesario un proceso de traducción para que los programas fuentes sean entendidos (ejecutados) Los compiladores son necesarios para el desarrollo de cualquier sistema, caso contrario tendríamos que programar en lenguaje ensamblador o peor aún en lenguaje máquina Compiladores 6 Lenguajes de programación Los lenguajes de programación han evolucionado Lenguaje de máquina: combinación de 1’s y 0’s Lenguaje montador (ensamblador): uso de mnemónicos y direcciones de memoria Lenguajes basados en cálculos numéricos: » ForTran, AlGol, PL/1, Pascal, Basic Lenguajes para los negocios: COBOL Lenguajes para inteligencia artificial: LISP Lenguajes para sistemas: C Lenguajes orientados a objetos: C++, Object Pascal Lenguajes visuales: Entornos de desarrollo * Compiladores 7 Introducción Lenguaje Objeto: Conjunto de instrucciones que un computador entiende y ejecuta, es una combinación de 1’s y 0’s. Lenguaje ensamblador: Conjunto de instrucciones cuyas operaciones se especifican a través de mnemónicos que representan códigos de operaciones. Es como un lenguaje objeto codificado. Lenguaje de programación: Conjunto de instrucciones más cercanas al lenguaje humano y que permiten especificar algoritmos y estructuras de datos. Compiladores 8 Introducción Lenguajes de programación Lenguaje Objeto Lenguaje Natural Compiladores 9 Introducción Programa fuente Escrito en Lenguaje de programación Traductor Lenguaje Natural Ensambladores. Compiladores. Interpretes. Lenguaje Objeto Compiladores 10 Traducción La idea básica es tener un traductor que permita al computador “entender” (ejecutar) un programa escrito en un lenguaje de programación. Los softwares que realizan estas “traducciones” son conocidos como Compiladores En una visión simplista el compilador recibe una programa fuente y genera un programa objeto. Fuente Compilador Objeto Compiladores 11 Tipos Ensamblador: recibe un programa en lenguaje ensamblador y genera programa objeto. Compilador: recibe un programa escrito en un lenguaje de programación y genera un programa objeto. Intérprete: Analiza una instrucción fuente y la ejecuta directamente sin generar código objeto. Preprocesador. Permite sustituir macros, incluir archivos, entre otras funciones. Generalmente es un paso anterior a la traducción. Compiladores 12 Introducción Programa fuente assembly language Ensamblador Traductor Lenguaje Objeto Compiladores 13 Introducción Programa fuente Escrito en Lenguaje de Programación de A.N. Compilador Traductor Lenguaje Objeto Compiladores 14 Ambiente de compilación Luego de la generación del código objeto a veces es necesario importar algunas funciones estándares lo que significa combinar el código obtenido con otros previamente almacenados, de manera que se forme un código ejecutable y se pueda ubicar en la memoria para su ejecución. Los encargados de realizar estas funciones son conocidos como Cargadores y Editores de enlaces. Compiladores 15 Ambiente de compilación Fuente Compilador Objeto Cargador Ed. Enlace Ejecutable Compiladores 16 Cargadores y Editores de enlace Editor de enlace: permite formar un solo programa a partir de varios archivos de código de máquina reubicables, ya obtenidos de compilaciones o almacenados en bibliotecas de rutinas. Cargadores: Resuelve el problema de direcciones absolutas y/o programas reentrantes. Tiene como objetivo ubicar el programa en memoria para su ejecución. Compiladores 17 * Modelo En el proceso de traducción existen dos tareas básicas: Análisis (front-end): El texto de entrada es examinado, verificado y comprendido. Generación de Código (back-end): Se genera código objeto equivalente a la cadena de entrada. Esto permite que: Front-end y back-end se comuniquen solo a través de la representación intermedia. Front-end depende sólo del lenguaje fuente (independe del lenguaje objeto) Back-end depende exclusivamente del lenguaje objeto (independe del lenguaje fuente). Compiladores * 18 Análisis Normalmente asociamos la Sintaxis a la idea de forma, mientras que la Semántica se asocia a significado o contenido. Así la sintaxis de un lenguaje debe describir todos los aspectos relativos a la forma de construcción de programas correctos, mientras que la semántica debe describir lo que sucede cuando el programa es ejecutado. Teóricamente sólo los programas correctos pertenecen al lenguaje y los programas incorrectos no tienen interés. Un programa o es del lenguaje (está correcto) o no es del lenguaje (no está correcto). Compiladores 19 Análisis Pero en la práctica cuando un programa no está correcto un buen compilador debe indicar este hecho y tratar de ayudar al usuario a transformalo en un programa correcto. El tratamiento de errores incluye mensajes informativos y una recuperación del error para que el análisis continúe y otros errores sean detectados. Normalmente el análisis se divide en léxico, sintáctico y semántico. Compiladores 20 Fases de un compilador Las fases de un compilador son: Análisis léxico Análisis Sintáctico Análisis Semántico Generación de código Compiladores 21 Fases de un compilador Analizador Léxico Analizador Sintáctico Administrador de la tabla de símbolos Analizador Semántico Generador de Código Manejador de errores Optimizador de Código Compiladores 22 Analizador Léxico Tiene como finalidad la separación e identificación de los elementos que componen un programa, igualmente elimina los elementos “decorativos “ como blancos, comentarios, etc. Como resultado de este análisis los items como identificadores, palabras reservadas, operadores, etc. son reconocidos (comúnmente a través de un código y una cadena de caracteres). Compiladores 23 * Analizador Léxico Ejemplo: if x > 0 then modx := x {x es positivo} else modx := (-x) {x es negativo} Compiladores 24 Analizador Léxico Luego del análisis léxico tendríamos la secuencia de elementos (tokens): Tipo de token palabra reservada if identificador operador mayor literal numérico palabra reservada then identificador operador de atribución Valor del token if x > 0 then modx := Compiladores 25 Analizador Léxico Normalmente los tipos de tokens se representan con valores de un tipo enumerado o con valores numéricos Generalmente la implantación de un analizador léxico (scanner) se basa en un autómata finito que reconoce las diferentes construcciones. El análisis léxico puede ser complicado dependiendo del lenguaje fuente, por ejemplo Fortran no tiene palabras reservadas, permite el uso de blancos en los identificadores entre otras características que hacen que el scanner sea bastante complejo. Compiladores 26 Analizador Léxico Ejemplo: DO 10 I = 5 Puede ser el inicio de un comando de asignación DO 10 I = 5. Puede ser el inicio de un comando repetitivo DO 10 I = 5, 10 Note que sólo cuando aparece el . o la ; se sabe con seguridad que comando estamos analizando. La mayoría de los lenguajes modernos trata de simplificar la implantación de los scanners Compiladores 27 Analizador Sintáctico También conocido como Parser. Tiene como finalidad reconocer la estructura global del programa, verificando que los comandos, declaraciones, expresiones cumplan con las reglas de composición (está bien escrito). Para el programa presentado antes se debería reconocer que se trata de un <comando>, mas específicamente de un <comando-if> compuesto de la palabra reservada if, seguido de una <expresión>, seguida de la palabra reservada then, etc. Compiladores 28 * Analizador Sintáctico Normalmente las reglas de formación de los lenguajes se expresan mediante reglas gramaticales (que se conoce como gramática de un lenguaje) Existen tipos de gramáticas y a cada uno corresponde una forma de realizar el análisis sintáctico, lo que da origen a los diferentes métodos de parsers. Un aspecto importante de los analizadores sintácticos es la recuperación de errores es decir una vez detectado el error ¿cómo continuar con el análisis sintáctico? Compiladores 29 Analizador Sintáctico Ejemplo: forb I := 1 to 10 do …..; Si se detecta el error en el identificador forb seguido de otro identificador I (no es inicio de ningún comando válido), podemos considerar forb como error y asumir que I es el inicio de una asignación; cuando se encuentra la palabra reservada to nuevamente dará error puesto que una asignación no contiene esa palabra reservada. Es claro que seguiremos generando errores (que no existen) como consecuencia de una palabra reservada mal escrita y principalmente de una recuperación no muy correcta. Compiladores 30 Analizador Semántico Básicamente el análisis Semántico trata los aspectos sensibles al contexto de la sintaxis de los lenguajes de programación. Por ejemplo no se puede representar en una gramática libre de contexto una regla como: “todo identificador debe ser declarado antes de ser usado”, es responsabilidad del análisis semántico verificar si esa regla se cumplió o no. En muchos casos un gran apoyo para esta fase es la denominada Tabla de símbolos Compiladores * 31 Analizador Semántico Se dice que el análisis semántico es dirigido por la sintaxis: se asocia a cada regla de la gramática una acción (acción semántica) que será ejecutada cada vez que se “señala” la regla. Estas acciones son implantadas frecuentemente como llamadas a rutinas semánticas y pueden ser responsables por realizar el análisis semántico y la generación de código. No existe una frontera definida entre lo que debe ser tratado por el análisis sintáctico o lo que debe ser tratado por el análisis semántico, quedando esta decisión a criterio del diseñador del compilador. Compiladores 32 Tabla de Símbolos También conocida como tabla de identificadores Es una estructura de datos que contiene un registro por cada identificador del programa (con sus atributos). Esta estructura permite encontrar rápidamente el registro para cada identificador y almacenar o consultar de manera eficiente los campos dentro de ese registro. Por tanto guarda información sobre los identificadores (nombre, tipo, ámbito, dirección de memoria, número de parámetros y tipo si fuera un nombre de procedimiento, etc.) Compiladores 33 * Tabla de Símbolos Es una tabla muy importante por cuanto apoya a las diferentes etapas de la traducción. Su estructura y organización en general depende de las características del lenguaje fuente. Compiladores 34 Generación de código A veces se construye una representación intermedia del programa fuente para ser usada como base para la generación del código objeto. Suponiendo que la forma de ese código intermedio es buena entonces el proceso de generación de código depende sólo de la arquitectura de la máquina para la cual el código será generado. Las máquinas sencillas ofrecen pocas opciones y eso implica en un proceso de generación de código más directo. * Compiladores 35 Generación de código Ejemplo: para la expresión x := a + b * c Usando variables temporales para guardar operaciones intermedias, podemos generar el siguiente código, que aunque correcto puede ser optimizado: Compiladores 36 Generación de código Load b Mult c Store t1 Load a Add t1 Store t2 Load t2 Store x Podemos notar que hay instrucciones no necesarias. Un código optimizado sería: Compiladores 37 Generación de código Load b Mult c Add a Store x Este tipo de optimización de código se denomina dependiente de la máquina porque usa las características de la máquina para optimizar el código. Hay optimizaciones que son independientes de la máquina y cuya preocupación principal es analizar el programa más que las características de la máquina. Compiladores 38 Tratamiento de errores Un problema muy importante que hay resolver en cualquier fase de un traductor es la recuperación de errores. Es es: como continuar con el proceso una vez que se detectó un error de manera tal a no emitir mensajes de errores no existentes. Errores léxicos: abre “ y no se cierran Errores sintácticos: comandos mal escritos Errores semánticos: variable no declarada. Compiladores 39 Tratamiento de errores Se han realizado intentos para tener compiladores que no sólo se recuperen de un error sino mas bien traten de corregir los errores para continuar con un programa sin error que se pueda ejecutar. No se trata de un problema trivial y se tendrían que aplicar técnicas de inteligencia artificial Por ejemplo si tenemos: forb I := 10 ¿Cuál fue el error? ¿Variable mal escrita (forbI) o palabra reservada mal escrita (for)? Compiladores 40