Clase #5 - Diagramas T e Introducción Análisis Léxico [Compi

Clase #5 - Diagramas T e Introducción Análisis Léxico [Compi

Info iconThis preview shows page 1. Sign up to view the full content.

View Full Document Right Arrow Icon
This is the end of the preview. Sign up to access the rest of the document.

Unformatted text preview: Compiladores Compiladores e Interpretes Diagramas T, Introducción Análisis Léxico Luis Ochoa ziul1979@gmail.com Construcción de Compiladores La metáfora del dragón, aunque considerada obsoleta por algunos, ha dejado su marca en la materia e irrefutablemente se ha convertido en un punto de referencia Construcción de un Compilador Al hablar de un compilador, hay que especificar como mínimo los tipos de lenguajes siguientes: 1. El lenguaje fuente. 2. El lenguaje objeto y la plataforma de ejecución. 3. 3. El lenguaje en el que está escrito el propio compilador, denominado lenguaje de implementación. Ejemplo En un compilador de Fortran a código máquina del PC que se ejecuta sobre un PC: ▫ El lenguaje fuente sería el Fortran. ▫ El lenguaje objeto sería el código máquina del PC. ▫ El lenguaje en el que está escrito también sería código máquina del PC. Construcción de un Compilador • Para especificar formalmente estos lenguajes, el lenguaje en el que se escribirá el compilador debe ser un lenguaje que exista, y por lo tanto, sus estructuras y su funcionamiento ya son definidos y conocidos. • En lo que respecta al lenguaje objeto que se genera, hay algunas herramientas formales que nos permiten especificarlo, pero ninguna de uso amplio. • En cambio, el lenguaje fuente se especifica generalmente en tres partes: ▫ Especificación léxica. ▫ Especificación sintáctica. ▫ Especificación semántica. Construcción de un Compilador Especificación Léxica: Se lleva a cabo con expresiones regulares que definen los componentes léxicos del lenguaje (testigos). Utilizando estas definiciones se puede crear automáticamente un analizador léxico del lenguaje. Especificación Sintáctica: Se utiliza una gramática libre de contexto, escrita generalmente en notación BNF, para detallar la estructura sintáctica del lenguaje. A partir de esta gramática, y junto con el analizador léxico obtenido en la parte anterior, se puede generar, también automáticamente, un analizador sintáctico del lenguaje. Construcción de un Compilador Especificación Semántica: Se describe el significado de cada instrucción sintáctica y las reglas semánticas que se deben cumplir. Hay notaciones formales para especificar la semántica de un lenguaje, pero no se utilizan demasiado. La semántica suele ser especificada con palabras (lenguaje natural) y se programa manualmente. A veces, algunas reglas semánticas se pueden incluir dentro de la especificación sintáctica, por ejemplo, la procedencia y la asociatividad de los operadores. A partir de esta especificación semántica se construye el analizador semántico y se genera el código. Ámbitos de aplicación de un compilador Los compiladores son usados de manera continua en la programación de sistemas informáticos, por esto un buen conocimiento de su funcionamiento interno puede resultar fundamental para un profesional del área, y las técnicas aprendidas se pueden aplicar en entornos de diseño de software de diversos ámbitos, entre los que encontramos: ▫ Tratamiento de archivos con formato. ▫ Reconocimientos de formas, ya sean patrones de texto o del habla y visión por computador (con las técnicas del análisis sintáctico). ▫ Editores de estructuras (analizan programas a medida que se escriben y proponen auto- completacion del código). ▫ Calculo Simbólico. ▫ Traducción de archivos estructurados. ▫ Traductores entre diferentes idiomas. ▫ Etc. Diagramas de Tombstone • Ahora observaremos desde un alto nivel el diseño y la construcción de compiladores, al observar los pasos que deben seguirse desde el momento en que se escribe un compilador en un lenguaje de programación concreto hasta que se obtiene el compilador para una determinada plataforma. • Para esto usaremos los diagramas de Tombstone, también denominados diagramas T, los cuales permiten observar todo este proceso de manera visual. Principales Componentes de los diagramas de Tombstone • Compilador: Los tres diagramas que definen un compilador se pueden representar utilizando un diagrama T o de Tombstone. Principales Componentes de los diagramas de Tombstone • Por ejemplo, un compilador de Pascal a código máquina de PC (EXE-PC), escrito en código máquina de PC, se representaría de la manera siguiente: • Un compilador de C a código objeto escrito en C se representaría de la manera siguiente: Principales Componentes de los diagramas de Tombstone • Programa: representa el programa P escrito en el lenguaje L. Por ejemplo el programa ordenar escrito en los lenguajes pascal y C. Principales Componentes de los diagramas de Tombstone • Máquina: Indica la plataforma de ejecución, el sistema operativo o el computador de ejecución M. Por ejemplo, una maquina VAX, un MAC o un Linux para PC: Principales Componentes de los diagramas de Tombstone • Intérprete: Representa el interprete del lenguaje L escrito en M. Por ejemplo, un intérprete de Basic para PC o un intérprete de SQL para VAX: Principios básicos de los diagramas de Tombstone • Usando distintas combinaciones de estos cuatro diagramas básicos se pueden diseñar los pasos necesarios para la creación de los compiladores o para la ejecución de programas. • Siendo garantizada la combinación correcta en el proceso de construcción por las reglas de unión de los diagramas T. Principios básicos de los diagramas de Tombstone • Para unir dos diagramas se utilizan unas reglas de unión básicas que aseguran su coherencia: dos diagramas se pueden unir si los lenguajes que se tocan en el punto de unión son iguales: Principios básicos de los diagramas de Tombstone Reglas: • Para la ejecución de un programa P escrito en lenguaje M, la plataforma de ejecución debe ser el mismo M. • Si el programa P escrito en L se interpreta, el intérprete debe tener el mismo lenguaje (L) de interpretación (fuente) y su lenguaje de implementación debe ser igual que la máquina en la que se ejecuta (M). • Al compilar un programa P escrito en L1, el compilador debe tener como lenguaje fuente L1. El lenguaje con el que está escrito el compilador debe coincidir con la máquina en la que se ejecuta el compilador (M), y la salida de la compilación será el programa P escrito en L2, el lenguaje objeto del compilador. Principios básicos de los diagramas de Tombstone Ejemplos de unión de dos diagramas: Principios básicos de los diagramas de Tombstone Ejemplo de unión de dos compiladores: Imaginemos que Imaginemos que escribimos en C un un compilador de Pascal a Pentium (1). ▫ Disponemos de un compilador de C a Pentium escrito en Pentium (2). ▫ Disponemos del Pentium. ▫ El resultado de la compilación de (1) sobre (2) sería un compilador de Pascal a Pentium escrito en Pentium. Principios básicos de los diagramas de Tombstone Posible solución de la unión de dos compiladores: Técnicas diversas de creación de compiladores con diagramas de Tombstone Compilador Enlazador: Esta técnica divide el proceso de traducción en dos partes: ▫ En la primera, utiliza un traductor del lenguaje fuente fuente a un código intermedio. ▫ En la segunda, utiliza otro para traducir del código intermedio al lenguaje objeto. Como ya hemos visto, esta división resulta muy útil cuando se deben desarrollar diferentes compiladores para distintas plataformas. Técnicas diversas de creación de compiladores con diagramas de Tombstone Compilador Enlazador: 1. Traduce a código intermedio. 2. Traduce a código objeto. Técnicas diversas de creación de compiladores con diagramas de Tombstone Compilador Cruzado: Esta técnica genera código objeto para un computador distinto del que lleva a cabo la compilación. Muchas veces hay que desarrollar compiladores para máquinas que todavía no existen, y es entonces cuando se utiliza ampliamente esta técnica. La idea consiste en escribir con L el compilador (1) para la nueva máquina MX que todavía no tenemos disponible. Una vez escrito, se compila en una máquina que tenga disponible un compilador de L (2) y obtenemos un compilador que genera código para la nueva máquina MX, pero que se ejecuta en la vieja M (3). Técnicas diversas de creación de compiladores con diagramas de Tombstone • Compilador Cruzado: Es el que se crea en una máquina diferente de aquélla para la que genera código. Primera fase: Técnicas diversas de creación de compiladores con diagramas de Tombstone • Compilador Cruzado: Si el compilador escrito (1) se compila una segunda vez sobre el compilador creado (3), se obtiene un compilador que genera código para la nueva máquina y, además, se ejecuta en la nueva máquina (4). Segunda fase: Técnicas diversas de creación de compiladores con diagramas de Tombstone Compilador Cruzado: Todo este proceso se ve claramente en la figura siguiente. Para crear un compilador de Pascal para una nueva máquina M5, se trabaja sobre la que ya hay (M4), que ya tiene tiene un compilador de Pascal. Una vez creado el compilador, todos los programas escritos en Pascal que había para M4 se pueden recompilar, y cuando la máquina M5 esté disponible ya habrá toda una biblioteca de programas que funcionarán con ésta. Técnicas diversas de creación de compiladores con diagramas de Tombstone Compilador Cruzado: Técnicas diversas de creación de compiladores con diagramas de Tombstone Bootstrapping: • Un autocompilador está escrito en el mismo lenguaje que se quiere compilar. Por ejemplo, si se quiere crear la versión 8 del compilador de Fortran y se tiene disponible el Fortran 7, el nuevo compilador debería generar un código de ejecución más rápido y eficiente que el de la versión anterior. • Dado que ya hay muchas máquinas que disponen de Fortran 7, es mejor desarrollar la nueva versión en Fortran. De esta manera, el compilador de Fortran 8 podrá crearse y estar disponible en todas las máquinas que ya tienen el compilador de Fortran 7. Resumiendo: Es el que está escrito en el mismo lenguaje que se quiere compilar. Técnicas diversas de creación de compiladores con diagramas de Tombstone Bootstrapping: • En general, cuando se desarrolla un nuevo compilador, se parte de una base ya existente y se aprovecha la organización conocida para acelerar el proceso: normalmente se aprovecha algún lenguaje de alto nivel para crear el nuevo compilador. • La idea final es que el lenguaje de desarrollo sea el mismo lenguaje fuente. Para hacerlo, se suele partir de una versión reducida del lenguaje fuente y se mejora por recompilación de sí misma. Resumiendo: Se parte de una versión reducida y se mejora en diferentes pasos de compilación. Técnicas diversas de creación de compiladores con diagramas de Tombstone Bootstrapping: Incluso en el caso de partir de cero, lo mejor es obtener el compilador por un procedimiento incremental, pero desarrollado en el mismo lenguaje fuente. Por ejemplo, si se quiere crear un compilador de Ada para Mac y tenemos un Mac y un compilador de C para Mac, se trataría de lo siguiente: 1. Crear un compilador de una versión reducida del lenguaje Ada, escrito en C y que genere código Mac (1). 2. Crear un compilador de Ada escrito en el lenguaje Ada reducido (2). 3. Crear un compilador de Ada escrito en Ada (3). Generará buen código, pero no se ejecutará eficientemente. 4. Compilar el programa fuente de (3) con el compilador obtenido en el paso 3 para obtener un compilador que, además de generar buen código, se ejecutará más eficientemente. Técnicas diversas de creación de compiladores con diagramas de Tombstone Bootstrapping: Otra aplicación de esta técnica consiste en mejorar la eficiencia de los compiladores. Imaginemos que tenemos un compilador de Pascal que genera código poco eficiente; los pasos para crear un compilador que genere código eficiente serían los siguientes: 1. Escribir el compilador que genera código eficiente, M+, (1) y compilarlo con el antiguo (2). Tendremos un nuevo compilador (3) que genera código eficiente, pero se ejecuta de manera ineficiente. 2. Compilar otra vez el compilador escrito (1) sobre el compilador creado en el primer paso (3). Ahora tenemos un compilador (4) que genera buen código y se ejecuta eficientemente. Técnicas diversas de creación de compiladores con diagramas de Tombstone Bootstrapping: Técnicas diversas de creación de compiladores con diagramas de Tombstone Compiladores Interpretativos: A veces, interesa compilar rápidamente el programa fuente para comprobar que esté libre de errores y generar, al mismo tiempo, un código intermedio sencillo. Después, este código intermedio puede ser distribuido a diferentes plataformas en las que se utilizará un intérprete para ejecutarlo. La unión del intérprete y la plataforma de ejecución se suele denominar máquina virtual. Técnicas diversas de creación de compiladores con diagramas de Tombstone Compiladores Interpretativos: La unión de un intérprete de Basic y un PC quedaría como sigue: Técnicas diversas de creación de compiladores con diagramas de Tombstone Compiladores Interpretativos: Por ejemplo, Java es un ejemplo de compilador interpretativo; primero se compila para dejar un código libre de errores y después se utiliza la Java Virtual Machine (JVM) de cada plataforma para interpretar el código intermedio: Ejercicios Propuestos 1. Se quieren crear nuevos compiladores de los lenguajes: ▫ Fortran, Ada y Modula para las plataformas de ejecución: ▫ P1, P2, P3 y P4. En cada una dispones de: ▫ Un compilador de C a código objeto ▫ De un enlazador de código objeto a lenguaje máquina. • Diseña, utilizando diagramas de Tombstone, la estrategia de desarrollo más rápida de estos compiladores (la que lleve menos tiempo de programación). • Muestra cuál sería el proceso para generar el ejecutable de un programa escrito en Fortran para la plataforma P2. Ejercicios Propuestos 2. Se quiere desarrollar un compilador de Prolog para código Pentium IV. • Dispones para ello de un intérprete de Prolog y de un computador Pentium IV. • Muestra con diagramas de Tombstone la estrategia estrategia que minimice el trabajo del programador. Ejercicios Propuestos 3. Se desarrolla un nuevo procesador P8 más potente que el actual P7. Para poder aprovechar las nuevas capacidades de P8, deben recompilarse todas las aplicaciones escritas en Delphi Delphi existentes en P7. • Diseña, con la ayuda de diagramas de Tombstone, la estrategia que permita hacer este paso de las aplicaciones Delphi de P7 a P8 sin tener todavía el procesador P8 y disponiendo de P7 y de un compilador de Delphi para P7. • ¿Cómo se crearía el compilador de Delphi para P8? Ejercicios Propuestos 4. Se quiere crear una extensión del lenguaje C para trabajar con bases de datos sobre Mac. Denominaremos a este nuevo lenguaje C+. • Si dispones de un compilador de C para Mac y de un computador Mac, explica, con la ayuda de diagramas de Tombstone, cuál sería la estrategia de desarrollo más rápida para esta extensión de C. • ¿Cómo se crearía el ejecutable de un programa escrito en C+? Ejercicios Propuestos 5. Se tiene una versión antigua de Oberon para 486 y se quiere crear una nueva para Pentium III aprovechando todas las mejoras internas de la nueva máquina. • Describe la estrategia que proporcione el nuevo y eficiente compilador que se ejecute eficiente eficientemente en el Pentium III. • Sólo dispones del compilador de Oberon para 486 y del 486. Introducción Introducción al análisis léxico Introducción al análisis léxico • El analizador léxico, también denominado explorador o scanner, representa la primera fase de la ejecución del compilador. • Es el encargado de leer uno por uno los caracteres del archivo de entrada y agruparlos formando los lexemas de las entidades léxicas primarias, denominadas testigos (tokens). • Es decir, transforma los caracteres del programa de entrada en una secuencia de testigos que son transferidos al analizador sintáctico, denominado también parser o simplemente analizador, que es la segunda etapa del proceso de compilación. Introducción al análisis léxico Introducción al análisis léxico Por ejemplo en un archivo fuente podríamos encontrar: • Si a la entrada tenemos los caracteres ‘12573;’, el analizador léxico los leerá uno por uno y encontrará: encontrará: 1. El lexema ‘12573’ coincide con el patrón del testigo ‘CONSTANTE_ENTERA’ . 2. El lexema ’;’ coincide con el patrón del testigo ‘PUNTO_Y_COMA’. Introducción al análisis léxico • Si comparamos las expresiones (en las que reemplazamos los caracteres en blanco por el símbolo ‘•’ para leerlo mejor) ‘a•:=•b•*•7•+•4•;’ y ‘a•••:= b* 7+4•;’ • Vemos que la estructura de las dos expresiones es equivalente, pero que la posición de los caracteres que las componen, aunque siguen el mismo orden, son diferentes. Introducción al análisis léxico • Si comparamos también ‘a := b * 7 + 4 ;’ ‘d := b * 11 + 8 ;’ y • Vemos que las estructuras también son equivalentes y que sólo cambian los valores de los números y los nombres de las variables. De estos tres casos mostrados podemos deducir que diferentes distribuciones de caracteres dan lugar a una misma estructura de programa. Introducción al análisis léxico • Si las diferentes fases del compilador tuviesen que trabajar siempre directamente con los caracteres, descubrir la estructura del programa resultaría muy complicado. • Por este motivo, la primera fase del compilador, el analizador léxico, es la encargada del procesamiento de los caracteres del archivo de entrada. Introducción al análisis léxico Podemos decir que las tareas específicas del analizador léxico son las siguientes: 1. Reconocer los componentes léxicos del lenguaje. 2. 2. Eliminar los espacios en blanco, los caracteres de tabulación, los saltos de línea y de página y otros caracteres propios del dispositivo de entrada. 3. Eliminar los comentarios del programa fuente. Introducción al análisis léxico 4. Reconocer los identificadores de variable, tipo, constantes, etc. y guardarlos en la tabla de símbolos. 5. 5. Avisar de los errores léxicos que detecte. 6. Relacionar los mensajes de error del compilador con el lugar en el que aparecen en el programa fuente (línea, columna, etc.). ¿Por qué separar el analizador léxico del sintáctico? • Generalmente esto se lleva a cabo para aumentar la eficiencia de la verificación de un programa fuente, al permitirle al analizador léxico que solo se concentre en filtrar el archivo de entrada, eliminando los caracteres innecesarios y proporcionando una secuencia equivalente de testigos al analizador sintáctico, que únicamente se concentrará en comprobar la estructura del programa fuente. Ventajas de separar el analizador léxico del sintáctico Con esta separación de tareas, obtenemos las ventajas siguientes: • Se simplifican las partes de análisis. El analizador sintáctico no tiene que preocuparse de recibir caracteres inesperados, ya que el archivo fuente es filtrado por el analizador léxico. • El diseño general se vuelve más claro y comprensible. Ventajas de separar el analizador léxico del sintáctico • Se mejora la eficiencia del compilador en conjunto, al poder aplicar técnicas más eficientes y depuradas en cada fase. Por ejemplo, es posible programar directamente en lenguaje de ensamblador las rutinas de lectura del programa fuente e incorporarles técnicas especiales de lectura que aumenten su velocidad. • Se aumenta la portabilidad del compilador. Si se desea cambiar alguna característica del alfabeto de entrada o de la plataforma de ejecución, basta con modificar la parte afectada del analizador léxico y dejar el analizador sintáctico intacto. Cuidados a tener en la implementación de una analizador léxico. • Se debe cuidar de hallar el testigo con el lexema más largo posible cuando lee caracteres del archivo fuente. Por ejemplo: Si a la entrada tenemos los caracteres ‘>b’, al encontrar el carácter ‘>’ hay que leer el carácter siguiente para determinar si se hace referencia al testigo MAYOR (>) o al testigo MAYOR_O_IGUAL (>=). • En el primer caso, el carácter leído por avanzado ‘b’ ha de ser devuelto al flujo de entrada, ya que puede ser el comienzo de un nuevo testigo. Por lo que se deben llevar a cabo lectura de caracteres por avanzada y retorno de los no usados al flujo de entrada. Cuidados a tener en la implementación de una analizador léxico. • En el mismo sentido, los identificadores suelen tener una longitud útil, de manera que dos identificadores sólo son distintos si se diferencian en su longitud útil. • Así pues, si la longitud útil es 6, “Valordereferencia” y “Valordeimpacto” representan el mismo identificador “Valord”. Ejemplo análisis léxico Si tenemos el siguiente código fuente: IF valor = 20 THEN valor := valor + 1; El analizador léxico generaría la siguiente secuencia de testigos: Ejemplo análisis léxico IF valor = 20 THEN valor := valor + 1; Secuencia Testigo 1 IF 2 IDENTIFICADOR 3 IGUALDAD 4 CONSTANTE_ENTERA 5 THEN 6 IDENTIFICADOR 7 ASIGNACIÓN 8 IDENTIFICADOR 9 SUMA 10 CONSTANTE_ENTERA 11 PUNTO_Y_COMA Ejemplo análisis léxico • Esta información basta para analizar la estructura de la secuencia, pero para verificar su significado semántico hace falta más información. • Así pues, hay que asociar un nuevo dato a cada testigo que nos proporcione esta información. De manera simple: además del valor del testigo, guardamos el valor del lexema o un apuntador en una posición de la tabla de símbolos en la que se guarda la información relacionada con el lexema (éste sería el caso de los identificadores de las variables, las constantes o los tipos). Tabla de símbolos: guarda información sobre los distintos identificadores: las variables, las constantes, los tipos, etc. Ejemplo análisis léxico Por lo tanto, el analizador sintáctico recibe realmente una pareja de datos: • El componente del lenguaje fuente identificado (testigo): una palabra reservada, un identificador, un operador lógico, un operador aritmético, etc. •Un registro con el conjunto de valores de atributos asociados. Lectura de archivos durante el análisis léxico • El analizador léxico es la única fase del compilador que debe leer el archivo fuente. • Para que sea lo más independiente posible del entorno, se encarga de esta lectura un módulo independiente altamente especializado: el sistema de entrada. • Así pues, si se quiere variar la plataforma de ejecución bastará con modificar sólo el sistema de entrada para que el analizador léxico funcione en la nueva plataforma Funcionamiento del analizador Léxico • La tarea principal del analizador léxico es proporcionar parejas testigo-atributos al analizador sintáctico. En esta línea, los dos analizadores suelen funcionar como una pareja productor-consumidor. • Por este motivo, el analizador léxico suele crearse como una función independiente que se activa cada vez que el analizador sintáctico pide un nuevo testigo. El analizador léxico produce testigos que el analizador sintáctico consume. Funcionamiento del analizador Léxico Funcionamiento del analizador Léxico • Cuando el analizador sintáctico hace una petición de un nuevo testigo al analizador léxico, éste analiza la cadena de entrada carácter a carácter hasta obtener un nuevo testigo, que retorna junto con sus atributos al analizador sintáctico. • Esta organización tiene una serie de ventajas: ▫ Al ser fases independientes, el analizador léxico y el analizador sintáctico pueden tener más nivel de especialización y pueden ser modificados sin afectar al resto del compilador. Funcionamiento del analizador Léxico ▫ El sistema de entrada puede optimizarse para leer una gran cantidad de caracteres del flujo de entrada en una sola operación. A partir de entonces, se aumenta la velocidad de acceso a los caracteres, al acceder a datos que están en la memoria del computador, más rápida que el disco. ▫ Las particularidades de leer el código fuente bajo distintas plataformas de ejecución quedan confinadas en el sistema de entrada del analizador léxico. Funcionamiento del analizador Léxico • Puesto que el código del analizador léxico es muy parecido en la mayoría de los compiladores de lenguajes de uso general, se puede reaprovechar cambiando sólo las tablas de definición del mismo lenguaje (palabras clave, operadores, etc.) y aumentar de este modo su portabilidad. REPASAR PARA LA PROXIMA CLASE • Lleve a cabo el curso de Java disponible en la página web de la materia (al menos la parte básica). • Repase las expresiones regulares, ya que las mismas serán usadas ahora de forma práctica y si no posee claro su concepto será difícil comprender la actividad de laboratorio llevada a cabo. ...
View Full Document

This note was uploaded on 05/13/2010 for the course REDES 7 taught by Professor Asesor during the Spring '10 term at UNE.

Ask a homework question - tutors are online