tutorial compiladores - ubuntu

68
Tutorías de Compiladores - Instalación UBUNTU Ingeniería de Sistemas TUTORIAL COMPILADORES Ing. CLARA LUCIA BURBANO GONZALEZ.

Upload: julio-cesar-leon-escobar

Post on 22-Mar-2016

254 views

Category:

Documents


1 download

DESCRIPTION

Manual de Instalacion Compiladores - UBUNTU

TRANSCRIPT

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

TUTORIAL COMPILADORES

Ing. CLARA LUCIA BURBANO GONZALEZ.

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

TUTORIAL DE COMPILADORES Presentación. Unidad 1. Compiladores Tema1: Historia. Tema 2: Definición, estructura y fases de un compilador. Tema 3: Clasificación de los compiladores. Tema 4: Funciones y partes en la que trabaja un compilador. Tema 5: Forma de examinar de un compilador. Tema 6: Como se sintetiza el código objeto en un compilador estándar, teórica y gráficamente generación de código. Tema 7: Análisis léxico. Tema 8: Análisis sintáctico. Tema 9: Análisis semántico. Unidad 2. Trabajos realizados en clase.

• Talleres.

• Exposiciones.

• Programas.

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

HISTORIA En 1946 se desarrolló el primer ordenador digital. En un principio, estas máquinas ejecutaban instrucciones consistentes en códigos numéricos que señalan a los circuitos de la máquina los estados correspondientes a cada operación. Esta expresión mediante códigos numéricos se llamó Lenguaje Máquina, interpretado por un secuenciador cableado o por un microprograma. Pero los códigos numéricos de las máquinas son engorrosos. Pronto los primeros usuarios de estos ordenadores descubrieron la ventaja de escribir sus programas mediante claves más fáciles de recordar que esos códigos numéricos; al final, todas esas claves juntas se traducían manualmente a Lenguaje Máquina. Estas claves constituyen los llamados lenguajes ensambladores, que se generalizaron en cuanto se dio el paso decisivo de hacer que las propias máquinas realizaran el proceso mecánico de la traducción. A este trabajo se le llama ensamblar el programa. Dada su correspondencia estrecha con las operaciones elementales de las máquinas, las instrucciones de los lenguajes ensambladores obligan a programar cualquier función de una manera minuciosa e iterativa. De hecho, normalmente, cuanto menor es el nivel de expresión de un lenguaje de programación, mayor rendimiento se obtiene en el uso de los recursos físicos (hardware). A pesar de todo, el lenguaje ensamblador seguía siendo el de una máquina, pero más fácil de manejar. Los trabajos de investigación se orientaron entonces hacia la creación de un lenguaje que expresara las distintas acciones a realizar de una manera lo más sencilla posible para el hombre. Así, en 1950, John Backus dirigió una investigación en I.B.M. en un lenguaje algebraico. En 1954 se empezó a desarrollar un lenguaje que permitía escribir fórmulas matemáticas de manera traducible por un ordenador. Le llamaron FORTRAN (FORmulae TRANslator). Fue el primer lenguaje considerado de alto nivel. Se introdujo en 1957 para el uso de la computadora IBM modelo 704. Permitía una programación más cómoda y breve que lo existente hasta ese momento, lo que suponía un considerable ahorro de trabajo. Surgió así por primera vez el concepto de un traductor, como un programa que traducía un lenguaje a otro lenguaje. En el caso particular de que el lenguaje a traducir es un lenguaje de alto nivel y el lenguaje traducido de bajo nivel, se emplea el término compilador. La tarea de realizar un compilador no fue fácil. El primer compilador de FORTRAN tardó 18 años-persona en realizarse y era muy sencillo. Este desarrollo del FORTRAN estaba muy influenciado por la máquina objeto en la que iba a ser implementado. Como un ejemplo de ello tenemos el hecho de que los espacios en blanco fuesen ignorados, debido a que el periférico que se utilizaba como entrada de programas (una lectora de tarjetas perforadas) no contaba correctamente los espacios en blanco. Paralelamente al desarrollo de FORTRAN en América, en Europa surgió una corriente más universitaria, que pretendía que la definición de un lenguaje fuese independiente de la máquina y en donde los algoritmos se pudieran expresar de forma más simple. Esta corriente estuvo muy influida por los trabajos sobre gramáticas de contexto libre publicados por Chomsky dentro de su estudio de lenguajes naturales. Con estas ideas surgió un grupo europeo encabezado por el profesor F.L.Bauer (de la Universidad de Munich). Este grupo definió un lenguaje de usos múltiples independiente de una realización concreta sobre una máquina. Pidieron colaboración a la asociación americana A.C.M. (Association for Computing Machinery) y se formó un comité en el que participó

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

J. Backus que colaboraba en esta investigación. De esa unión surgió un informe definiendo un International Algebraic Language (I.A.L.), publicado en Zurich en 1958. Posteriormente este lenguaje se llamó ALGOL 58 (ALGOritmic Language). En 1969, el lenguaje fue revisado y llevó a una nueva versión que se llamó ALGOL 60. La versión actual es ALGOL 68, un lenguaje modular estructurado en bloques. En el ALGOL aparecen por primera vez muchos de los conceptos de los nuevos lenguajes algorítmicos: Definición de la sintaxis en notación BNF (Backus-Naur Form). Formato libre. Declaración explícita de tipo para todos los identificadores. Estructuras iterativas más generales. Recursividad. Paso de parámetros por valor y por nombre. Estructura de bloques, lo que determina la visibilidad de los identificadores. Junto a este desarrollo en los lenguajes, también se iba avanzando en la técnica de compilación. En 1958, Strong y otros proponían una solución al problema de que un compilador fuera utilizable por varias máquinas objeto. Para ello, se dividía por primera vez el compilador en dos fases, designadas como el #"front end" y el "back end". La primera fase (front end) es la encargada de analizar el programa fuente y la segunda fase (back end) es la encargada de generar código para la máquina objeto. El puente de unión entre las dos fases era un lenguaje intermedio que se designó con el nombre de UNCOL (UNiversal Computer Oriented Language). Para que un compilador fuera utilizable por varias máquinas bastaba únicamente modificar su back end. Aunque se hicieron varios intentos para definir el UNCOL, el proyecto se ha quedado simplemente en un ejercicio teórico. De todas formas, la división de un compilador en front end y back end fue un adelanto importante. Ya en estos años se van poniendo las bases para la división de tareas en un compilador. Así, en 1959 Rabin y Scott proponen el empleo de autómatas deterministas y no deterministas para el reconocimiento lexicográfico de los lenguajes. Rápidamente se aprecia que la construcción de analizadores léxicos a partir de expresiones regulares es muy útil en la implementación de los compiladores. En 1968, Johnson apunta diversas soluciones. En 1975, con la aparición de LEX surge el concepto de un generador automático de analizadores léxicos a partir de expresiones regulares, basado en el sistema operativo UNIX. A partir de los trabajos de Chomsky ya citados, se produce una sistematización de la sintaxis de los lenguajes de programación, y con ello un desarrollo de diversos métodos de análisis sintáctico. Con la aparición de la notación BNF - desarrollada en primer lugar por Backus en 1960 cuando trabajaba en un borrador del ALGOL 60, modificada en 1963 por Naur y formalizada por Knuth en 1964 - se tiene una guía para el desarrollo del análisis sintáctico. Los diversos métodos de parsers ascendentes y descendentes se desarrollan durante la

#

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

década de los 60. En 1959, Sheridan describe un método de parsing de FORTRAN que introducía paréntesis adicionales alrededor de los operandos para ser capaz de analizar las expresiones. Más adelante, Floyd introduce la técnica de la precedencia de operador y el uso de las funciones de precedencia. A mitad de la década de los 60, Knuth define las gramáticas LR y describe la construcción de una tabla canónica de parser LR. Por otra parte, el uso por primera vez de un parsing descendente recursivo tuvo lugar en el año 1961. En el año 1968 se estudian y definen las gramáticas LL así como los parsers predictivos. También se estudia la eliminación de la recursión a la izquierda de producciones que contienen acciones semánticas sin afectar a los valores de los atributos. En los primeros años de la década de los 70, se describen los métodos SLR y LALR de parser LR. Debido a su sencillez y a su capacidad de análisis para una gran variedad de lenguajes, la técnica de parsing LR va a ser la elegida para los generadores automáticos de parsers. A mediados de los 70, Johnson crea el generador de analizadores sintácticos YACC para funcionar bajo un entorno UNIX. Junto al análisis sintáctico, también se fue desarrollando el análisis semántico. En los primeros lenguajes (FORTRAN y ALGOL 60) los tipos posibles de los datos eran muy simples, y la comprobación de tipos era muy sencilla. No se permitía la coerción de tipos, pues ésta era una cuestión difícil y era más fácil no permitirlo. Con la aparición del ALGOL 68 se permitía que las expresiones de tipo fueran construidas sistemáticamente. Más tarde, de ahí surgió la equivalencia de tipos por nombre y estructural. El manejo de la memoria como una implementación tipo pila se usó por primera vez en 1958 en el primer proyecto de LISP. La inclusión en el ALGOL 60 de procedimientos recursivos potenció el uso de la pila como una forma cómoda de manejo de la memoria. Dijkstra introdujo posteriormente el uso del display para acceso a variables no locales en un lenguaje de bloques. También se desarrollaron estrategias para mejorar las rutinas de entrada y de salida de un procedimiento. Así mismo, y ya desde los años 60, se estudió el paso de parámetros a un procedimiento por nombre, valor y variable. Con la aparición de lenguajes que permiten la localización dinámica de datos, se desarrolla otra forma de manejo de la memoria, conocida por el nombre de heap (montículo). Se han desarrollado varias técnicas para el manejo del heap y los problemas que con él se presentan, como son las referencias perdidas y la recogida de basura. La técnica de la optimización apareció desde el desarrollo del primer compilador de FORTRAN. Backus comenta cómo durante el desarrollo del FORTRAN se tenía el miedo de que el programa resultante de la compilación fuera más lento que si se hubiera escrito a mano. Para evitar esto, se introdujeron algunas optimizaciones en el cálculo de los índices dentro de un bucle. Pronto se sistematizan y se recoge la división de optimizaciones independientes de la máquina y dependientes de la máquina. Entre las primeras están la propagación de valores, el arreglo de expresiones, la eliminación de redundancias, etc. Entre las segundas se podría encontrar la localización de registros, el uso de instrucciones propias de la máquina y el reordenamiento de código. A partir de 1970 comienza el estudio sistemático de las técnicas del análisis de flujo de datos. Su repercusión ha sido enorme en las técnicas de optimización global de un programa. En la actualidad, el proceso de la compilación ya está muy asentado. Un compilador es una herramienta bien conocida, dividida en diversas fases. Algunas de estas fases se pueden generar automáticamente

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

(analizador léxico y sintáctico) y otras requieren una mayor atención por parte del escritor de compiladores (las partes de traducción y generación de código). De todas formas, y en contra de lo que quizá pueda pensarse, todavía se están llevando a cabo varias vías de investigación en este fascinante campo de la compilación. Por una parte, se están mejorando las diversas herramientas disponibles (por ejemplo, el generador de analizadores léxicos Aardvark para el lenguaje PASCAL). También la aparición de nuevas generaciones de lenguajes -ya se habla de la quinta generación, como de un lenguaje cercano al de los humanos-ha provocado la revisión y optimización de cada una de las fases del compilador. El último lenguaje de programación de amplia aceptación que se ha diseñado, el lenguaje Java, establece que el compilador no genera código para una máquina determinada sino para una virtual, la Java Virtual Machine (JVM), que posteriormente será ejecutado por un intérprete, normalmente incluido en un navegador de Internet. El gran objetivo de esta exigencia es conseguir la máxima portabilidad de los programas escritos y compilados en Java, pues es únicamente la segunda fase del proceso la que depende de la máquina concreta en la que se ejecuta el intérprete. DEFINICIÓN, ESTRUCTURA Y FASES DE UN COMPILADOR. ¿Qué es un compilador? Un traductor es cualquier programa que toma como entrada un texto escrito en un lenguaje, llamado fuente y da como salida otro texto en un lenguaje, denominado objeto. Compilador En el caso de que el lenguaje fuente sea un lenguaje de programación de alto nivel y el objeto sea un lenguaje de bajo nivel (ensamblador o código de máquina), a dicho traductor se le denomina compilador. Un ensamblador es un compilador cuyo lenguaje fuente es el lenguaje ensamblador. Un intérprete no genera un programa equivalente, sino que toma una sentencia del programa fuente en un lenguaje de alto nivel y la traduce al código equivalente y al mismo tiempo lo ejecuta. Históricamente, con la escasez de memoria de los primeros ordenadores, se puso de moda el uso de intérpretes frente a los compiladores, pues el programa fuente sin traducir y el intérprete juntos daban una ocupación de memoria menor que la resultante de los compiladores. Por ello los primeros ordenadores personales iban siempre acompañados de un intérprete de BASIC (Spectrum, Commodore VIC-20, PC XT de IBM, etc.). La mejor información sobre los errores por parte del compilador así como una mayor velocidad de ejecución del código resultante hizo que poco a poco se impusieran los compiladores. Hoy en día, y con el problema de la memoria prácticamente resuelto, se puede hablar de un gran predominio de los compiladores frente a los intérpretes, aunque intérpretes como los incluidos en los navegadores de Internet para interpretar el código JVM de Java son la gran excepción. Ventajas de compilar frente a interpretar: Se compila una vez, se ejecuta n veces. En bucles, la compilación genera código equivalente al bucle, pero interpretándolo se traduce tantas veces una línea como veces se repite el bucle.

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

El compilador tiene una visión global del programa, por lo que la información de mensajes de error es más detallada. Ventajas del intérprete frente al compilador: Un intérprete necesita menos memoria que un compilador. En principio eran más abundantes dado que los ordenadores tenían poca memoria. Permiten una mayor interactividad con el código en tiempo de desarrollo. Un compilador no es un programa que funciona de manera aislada, sino que necesita de otros programas para conseguir su objetivo: obtener un programa ejecutable a partir de un programa fuente en un lenguaje de alto nivel. Algunos de esos programas son el preprocesador, el linker, el depurador y el ensamblador. El preprocesador se ocupa (dependiendo del lenguaje) de incluir ficheros, expandir macros, eliminar comentarios, y otras tareas similares. El linker se encarga de construir el fichero ejecutable añadiendo al fichero objeto generado por el compilador las cabeceras necesarias y las funciones de librería utilizadas por el programa fuente. El depurador permite, si el compilador ha generado adecuadamente el programa objeto, seguir paso a paso la ejecución de un programa. Finalmente, muchos compiladores, en vez de generar código objeto, generan un programa en lenguaje ensamblador que debe después convertirse en un ejecutable mediante un programa ensamblador. ESTRUCTURA DE UN COMPILADOR

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

Cualquier compilador debe realizar dos tareas principales: análisis del programa a compilar y síntesis de un programa en lenguaje maquina que, cuando se ejecute, realizara correctamente las actividades descritas en el programa fuente. Para el estudio de un compilador, es necesario dividir su trabajo en fases. Cada fase representa una transformación al código fuente para obtener el código objeto. La siguiente figura representa los componentes en que se divide un compilador. Las tres primeras fases realizan la tarea de análisis, y las demás la síntesis.

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

En cada una de las fases se utiliza un administrador de la tabla de símbolos y un manejador de errores. Análisis Léxico. En la fase de análisis léxico se leen los caracteres del programa fuente y se agrupan en cadenas que representan los componentes léxicos. Cada componente léxico es una secuencia lógicamente coherente de caracteres relativa a un identificador, una palabra reservada, un operador o un carácter de puntuación. A la secuencia de caracteres que representa un componente léxico se le llama lexema (o con su nombre en inglés token). En el caso de los identificadores creados por el programador no solo se genera un componente léxico, sino que se genera otro lexema en la tabla de símbolos. Análisis Sintáctico. En esta fase, los componentes léxicos se agrupan en frases gramaticales que el compilador utiliza para sintetizar la salida. Análisis Semántico. La fase de análisis semántico se intenta detectar instrucciones que tengan la estructura sintáctica correcta, pero que no tengan significado para la operación implicada. Generación de código Intermedio. Algunos compiladores generan una representación intermedia explícita del programa fuente, una vez que se han realizado las fases de análisis. Se puede considerar esta operación intermedia como un subprograma para una máquina abstracta. Esta representación intermedia debe tener dos propiedades importantes: debe ser fácil de producir y fácil de traducir al programa objeto. Optimización de Código. En esta fase se trata de mejorar el código intermedio, de modo que resulte un código de máquina más rápido de ejecutar. Generación de Código. Esta constituye la fase final de un compilador. En ella se genera el código objeto que por lo general consiste en código en lenguaje máquina (código relocalizable) o código en lenguaje ensamblador. Administrador de la tabla de símbolos. Una tabla de símbolos es una estructura de datos que contiene un registro por cada identificador. El registro incluye los campos para los atributos del identificador. El administrador de la tabla de símbolos se encarga de manejar los accesos a la tabla de símbolos, en cada una de las etapas de compilación de un programa. Manejador de errores. En cada fase del proceso de compilación es posibles encontrar errores. Es conveniente que el tratamiento de los errores se haga de manera centralizada a través de un manejador de errores. De esta forma podrán controlarse más eficientemente los errores encontrados en cada una de las fases de la compilación de un programa.

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

CLASIFICACIÓN DE LOS COMPILADORES. El programa compilador traduce las instrucciones en un lenguaje de alto nivel a instrucciones que la computadora puede interpretar y ejecutar. Para cada lenguaje de programación se requiere un compilador separado. El compilador traduce todo el programa antes de ejecutarlo. Los compiladores son, pues, programas de traducción insertada en la memoria por el sistema operativo para convertir programas de cómputo en pulsaciones electrónicas ejecutables (lenguaje de máquina). Los compiladores pueden ser de: una sola pasada: examina el código fuente una vez, generando el código o programa objeto. pasadas múltiples: requieren pasos intermedios para producir un código en otro lenguaje, y una pasada final para producir y optimizar el código producido durante los pasos anteriores. Optimación: lee un código fuente, lo analiza y descubre errores potenciales sin ejecutar el programa. Compiladores incrementales: generan un código objeto instrucción por instrucción (en vez de hacerlo para todo el programa) cuando el usuario teclea cada orden individual. El otro tipo de compiladores requiere que todos los enunciados o instrucciones se compilen conjuntamente. Ensamblador: el lenguaje fuente es lenguaje ensamblador y posee una estructura sencilla. Compilador cruzado: se genera código en lenguaje objeto para una máquina diferente de la que se está utilizando para compilar. Es perfectamente normal construir un compilador de Pascal que genere código para MS-DOS y que el compilador funcione en Linux y se haya escrito en C++. Compilador con montador: compilador que compila distintos módulos de forma independiente y después es capaz de enlazarlos. Autocompilador: compilador que está escrito en el mismo lenguaje que va a compilar. Evidentemente, no se puede ejecutar la primera vez. Sirve para hacer ampliaciones al lenguaje, mejorar el código generado, etc. Metacompilador: es sinónimo de compilador de compiladores y se refiere a un programa que recibe como entrada las especificaciones del lenguaje para el que se desea obtener un compilador y genera como salida el compilador para ese lenguaje. El desarrollo de los metacompiladores se encuentra con la dificultad de unir la generación de código con la parte de análisis. Lo que sí se han desarrollado son generadores de analizadores léxicos y sintácticos. Por ejemplo, los conocidos: LEX = generador de analizadores léxicos YACC = generador de analizadores sintácticos desarrollados para UNIX. Los inconvenientes que tienen son que los analizadores que generan no son muy eficientes. Descompilador: es un programa que acepta como entrada código máquina y lo traduce a un lenguaje de alto nivel, realizando el proceso inverso a la compilación.

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

FUNCIONES Y PARTES EN LA QUE TRABAJA UN COMPILADOR. A grandes rasgos un compilador es un programa que lee un programa escrito es un lenguaje, el lenguaje fuente, y lo traduce a un programa equivalente en otro lenguaje, el lenguaje objeto. Como parte importante de este proceso de traducción, el compilador informa a su usuario de la presencia de errores en el programa fuente. A primera vista, la diversidad de compiladores puede parecer abrumadora. Hay miles de lenguajes fuente, desde los lenguajes de programación tradicionales, como FORTRAN o Pascal, hasta los lenguajes especializados que han surgido virtualmente en todas las áreas de aplicación de la informática. Los lenguajes objeto son igualmente variados; un lenguaje objeto puede ser otro lenguaje de programación o el lenguaje de máquina de cualquier computador entre un microprocesador y un supercomputador. A pesar de existir una aparente complejidad por la clasificación de los compiladores, como se vio en el tema anterior, las tareas básicas que debe realizar cualquier compilador son esencialmente las mismas. Al comprender tales tareas, se pueden construir compiladores para una gran diversidad de lenguajes fuente y máquinas objeto utilizando las mismas técnicas básicas. Nuestro conocimiento sobre cómo organizar y escribir compiladores ha aumentado mucho desde que comenzaron a aparecer los primeros compiladores a principios de los años cincuenta. Es difícil dar una fecha exacta de la aparición del primer compilador, porque en un principio gran parte del trabajo de experimentación y aplicación se realizó de manera independiente por varios grupos. Gran parte de los primeros trabajos de compilación estaba relacionada con la traducción de fórmulas aritméticas a código de máquina. En la década de 1950, se consideró a los compiladores como programas notablemente difíciles de escribir. EL primer compilador de FORTRAN, por ejemplo, necesitó para su implantación de 18 años de trabajo en grupo (Backus y otros [1975]). Desde entonces, se han descubierto técnicas sistemáticas para manejar muchas de las importantes tareas que surgen en la compilación. También se han desarrollado buenos lenguajes de implantación, entornos de programación y herramientas de software. Con estos avances, puede hacerse un compilador real incluso como proyecto de estudio en un curso de un semestre sobre diseño sobre de compiladores. PARTES EN LAS QUE TRABAJA UN COMPILADOR Conceptualmente un compilador opera en fases. Cada una de las cuales transforma el programa fuente de una representación en otra. En la figura se muestra una descomposición típica de un compilador. En la práctica se pueden agrupar fases y las representaciones intermedias entres las fases agrupadas no necesitan ser construidas explícitamente.

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

Las tres primeras fases forman la mayor parte de la porción de análisis de un compilador. La administración de la tabla se símbolos y el manejo de errores, se muestran en interacción con las seis fases de análisis léxico, análisis sintáctico, análisis semántico, generación de código intermedio, optimación de código y generación de código. De modo informal, también se llamarán “fases” al administrador de la tabla de símbolos y al manejador de errores. Administrador de la tabla de símbolos Una función esencial de un compilador es registrar los identificadores utilizados en el programa fuente y reunir información sobre los distintos atributos de cada identificador. Estos atributos pueden proporcionar información sobre la memoria asignada a un identificador, su tipo, su ámbito (la parte del programa donde tiene validez) y, en el caso de nombres de procedimientos, cosas como el número y tipos de sus argumentos, el método de pasar cada argumento (por ejemplo, por referencia) y el tipo que devuelve, si los hay. Una tabla de símbolos es una estructura de datos que contiene un registro por cada identificador, con los campos para los atributos del identificador. La estructura de datos permite encontrar rápidamente el registro de cada identificador y almacenar o consultar rápidamente datos en un registro

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

Cuando el analizador léxico detecta un identificador en el programa fuente, el identificador se introduce en la tabla de símbolos. Sin embargo, normalmente los atributos de un identificador no se pueden determinar durante el análisis léxico. Por ejemplo, en una declaración en Pascal como var posición, inicial, velocidad real; El tipo real no se conoce cuando el analizador léxico encuentra posición, inicial y velocidad. Las fases restantes introducen información sobre los identificadores en la tabla de símbolos y después la utilizan de varias formas. Por ejemplo, cuando se está haciendo el análisis semántico y la generación de código intermedio, se necesita saber cuáles son los tipos de los identificadores, para poder comprobar si el programa fuente los usa de una forma válida y así poder generar las operaciones apropiadas con ellos. El generador de código, por lo general, introduce y utiliza información detallada sobre la memoria asignada a los identificadores. Detección e información de errores Cada frase puede encontrar errores. Sin embargo, después de detectar un error. Cada fase debe tratar de alguna forma ese error, para poder continuar la compilación, permitiendo la detección de más errores en el programa fuente. Un compilador que se detiene cuando encuentra el primer error, no resulta tan útil como debiera. Las fases de análisis sintáctico y semántico por lo general manejan una gran proporción de los errores detectables por el compilador. La fase léxica puede detectar errores donde los caracteres restantes de la entrada no forman ningún componente léxico del lenguaje. Los errores donde la cadena de componentes léxicos violan las reglas de estructura (sintaxis) del lenguaje son determinados por la fase del análisis sintáctico. Durante el análisis semántico el compilador intenta detectar construcciones que tengan la estructura sintáctica correcta, pero que no tengan significado para la operación implicada, por ejemplo, si se intenta sumar dos identificadores. Uno de los cuales es el nombre de una matriz, y el otro, el nombre de un procedimiento. Las fases de análisis Conforme avanza la traducción, la representación interna del programa fuente que tiene el compilador se modifica. Para ilustrar esas representaciones, considérese la traducción de la proposición Posición:= inicial + velocidad * 60 (1) La figura muestra la representación de esa proposición después de cada frase. Posición := inicial + velocidad * 60

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

Id1 := id2 + id3 * 60 Tabla de símbolos 1 2 3 4 : = id1 + id2 * id3 entareal 60 temp1 := entareal(60) temp2 := id3 * temp1 temp3 := id2 + temp2 id1 := temp3 temp1 := id3 * 60.0 id1 := id2 + temp1 MOVF id3, R2 MULF #60.0, R2 MOVF id2, R1 ADDF R2, R1 MOVF R1, id1

Analizador léxico

Analizador sintáctico

Generador de código intermedio

Optimador de código

Generador de código

Posición ... Inicial ... Velocidad ...

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

Figura - Representación de una proposición.

La fase de análisis léxico lee los caracteres de un programa fuente y los agrupa en una cadena de componentes léxicos en los que cada componente representa una secuencia lógicamente coherente de caracteres, como un identificador, una palabra clave (if, while, etc), un carácter de puntuación, o un operador de varios caracteres, como :=. La secuencia de caracteres que forman un componente léxico se denomina lexema del componente. A ciertos componentes léxicos se les agregará un “valor léxico”. Así, cuando se encuentra un identificador como velocidad, el analizador léxico no sólo genera un componente léxico, por ejemplo, id, sino que también introduce el lexema velocidad en la tabla de símbolos, si aún no estaba allí. El valor léxico asociado con esta aparición de id señala la entrada de la tabla de símbolos correspondiente a velocidad. Usaremos id1 , id2 e id3 para posición, inicial y velocidad, respectivamente, para enfatizar que la representación interna de un identificador es diferente de la secuencia de caracteres que forman el identificador. Por tanto, la representación de (1) después del análisis léxico queda sugerida por: id1 := id2 + id3 * 60 (2) Se deberían construir componentes para el operador de varios caracteres := y el número 60, para reflejar su representación interna. En la sección IX ya se introdujeron las fases segunda y tercera: los análisis sintáctico y semántico. El análisis sintáctico impone una estructura jerárquica a la cadena de componentes léxicos, que se representará por medio de árboles sintácticos, como se muestra en la figura (a). Una estructura de datos típica para el árbol se muestra en la figura (b), en la que un nodo interior es un registro con un campo para el operador y dos campos que contienen apuntadores a los registros de los hijos izquierdo y derecho. Una hoja es un registro con dos o más campos, uno para identificar el componente léxico de la hoja, y los otros para registrar información sobre el componente léxico. Se puede tener información adicional sobre las construcciones del lenguaje añadiendo más campos a les registros de los nodos. : = id1 + id2 * id3 60 (a) := ¦ ¦ id ¦ 1 + ¦ ¦ id ¦ 1

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

* ¦ ¦ id ¦ 3 num¦60 (b)

Figuras - La estructura de datos en (b) corresponde al árbol en (a).

Generación de código intermedio Después de los análisis sintáctico y semántico, algunos compiladores generan una representación intermedia explícita del programa fuente. Se puede considerar esta representación intermedia como un programa para una máquina abstracta. Esta representación intermedia debe tener dos propiedades importantes; debe ser fácil de producir y fácil de traducir al programa objeto. La representación intermedia puede tener diversas formas. Existe una forma intermedia llamada “código de tres direcciones”, que es como el lenguaje ensamblador para una máquina en la que cada posición de memoria puede actuar como un registro. El código de tres direcciones consiste en una secuencia de instrucciones, cada una de las cuales tiene como máximo tres operandos. El programa fuente de (1) puede aparecer en código de tres direcciones como temp1 := entarea1(60) temp2 := id3 * temp1 (2) temp3 := id2 + temp2 id1 := temp3 Esta representación intermedia tiene varias propiedades. Primera, cada instrucción de tres direcciones tiene a lo sumo un operador, además de la asignación. Por tanto, cuando se generan esas instrucciones el compilador tiene que decidir el orden en que deben efectuarse, las operaciones; la multiplicación precede a la adición al programa fuente de (1). Segunda, el compilador debe generar un nombre temporal para guardar los valores calculados por cada instrucción. Tercera, algunas instrucciones de “tres direcciones” tienen menos de tres operadores, por ejemplo la primera y la última instrucciones de (2). Optimación de Código La fase de optimación de código trata de mejorar el código intermedio de modo que resulte un código de máquina más rápido de ejecutar. Algunas optimaciones son triviales. Por ejemplo, un algoritmo natural genera el código intermedio (2) utilizando una instrucción para cada operador de la representación del árbol después del análisis semántico, aunque hay una forma mejor de realizar los mismos cálculos usando las dos instrucciones Temp1 := id3 * 60.0 (3) Id1 := id2 + temp1 Este sencillo algoritmo no tiene nada de malo, puesto que el problema se puede solucionar en la fase de optimación de código. Esto es, el compilador puede deducir que la conversión

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

de 60 de entero a real se puede hacer de una vez por todas en el momento de la compilación, de modo que la operación entreal se puede eliminar. Además, temp3 se usa sólo una vez, para transmitir su valor a id1. Entonces resulta seguro sustituir a id1 por temp3, a partir de lo cual la última proposición de (2) no se necesita y se obtiene el código de (3). Hay muchas variaciones en la cantidad de optimación de código que ejecutan los distintos compiladores. En lo que hacen mucha optimación llamados “compiladores optimadores”, una parte significativa del tiempo del compilador se ocupa en esta fase. Sin embargo hay optimaciones sencillas que mejoran significativamente del tiempo del compilador se ocupa en esta fase. Sin embargo, hay optimaciones sencillas que mejoran sensiblemente el tiempo de ejecución del programa objeto sin retardar demasiado la compilación. FORMA DE EXAMINAR DE UN COMPILADOR. En la compilación hay dos partes: Análisis y Síntesis. La parte del análisis divide al programa fuente en sus elementos componentes y crea una representación intermedia. De las dos partes, la síntesis es la que requiere la técnica más especializada. Durante el análisis se determina las operaciones que implica el programa fuente y se registra en una estructura jerárquica llamada árbol. A menudo, se usa una clase especial de árbol llamado árbol sintáctico, donde cada nodo representa una operación y los hijos de un nodo son los argumentos de la operación. Por ejemplo, en la figura 5 se muestra un árbol sintáctico para una proposición de asignación. Figura (a) . Árbol sintáctico para posición := inicial + velocidad * 60 COMO SE SINTETIZA EL CÓDIGO OBJETO EN UN COMPILADOR ESTÁNDAR, TEÓRICA Y GRÁFICAMENTE GENERACIÓN DE CÓDIGO. En esta parte el código intermedio optimizado es traducido a una secuencia de instrucciones en ensamblador o en el código de máquina del procesador que nos interese. Por ejemplo, la sentencia A:=B+C se convertirá en: LOAD B ADD C STORE A suponiendo que estas instrucciones existan de esta forma en el ordenador de que se trate. Una conversión tan directa produce generalmente un programa objeto que contiene muchas cargas (loads) y almacenamientos (stores) redundantes, y que utiliza los recursos de la máquina de forma ineficiente. Existen técnicas para mejorar esto, pero son complejas. Una, por ejemplo, es tratar de utilizar al máximo los registros de acceso rápido que tenga la máquina. Así, en el procesador 8086 tenemos los registros internos AX, BX, CX, DX, etc. y podemos utilizarlos en vez de direcciones de memoria. Tabla De Símbolos Un compilador necesita guardar y usar la información de los objetos que se va encontrando en el texto fuente, como variables, etiquetas, declaraciones de tipos, etc. Esta información se almacena en una estructura de datos interna conocida como tabla de símbolos. El compilador debe desarrollar una serie de funciones relativas a la manipulación de esta tabla como insertar un nuevo elemento en ella, consultar la información relacionada con un

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

símbolo, borrar un elemento, etc. Como se tiene que acceder mucho a la tabla de símbolos los accesos deben ser lo más rápidos posible para que la compilación sea eficiente. Manejo de errores Es una de las misiones más importantes de un compilador, aunque, al mismo tiempo, es lo que más dificulta su realización. Donde más se utiliza es en las etapas de análisis sintáctico y semántico, aunque los errores se pueden descubrir en cualquier fase de un compilador. Es una tarea difícil, por dos motivos: A veces unos errores ocultan otros. A veces un error provoca una avalancha de muchos errores que se solucionan con el primero. Es conveniente un buen manejo de errores, y que el compilador detecte todos los errores que tiene el programa y no se pare en el primero que encuentre. Hay, pues, dos criterios a seguir a la hora de manejar errores: Pararse al detectar el primer error. Detectar todos los errores de una pasada. En el caso de un compilador interactivo (dentro de un entorno de desarrollo integrado, como Turbo-Pascal o Borland C++) no importa que se pare en el primer error detectado, debido a la rapidez y facilidad para la corrección de errores.

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

ANÁLISIS LÉXICO (scanner). CONCEPTO, FUNCIONES, ESPECIFICACIÓN Y RECONOCIMIENTO DE COMPONENTES LÉXICOS. En un compilador, el análisis lineal se llama análisis léxico o exploración. Esta fase se encarga de verificar si todas las cadenas pertenecen o no al lenguaje. Es decir realiza un análisis símbolo por símbolo indicando el token por cada uno de los elementos reconocidos o el error en caso de no reconocer. Este análisis no logra detectar muchos errores por su característica. En un programa fuente existen una serie de símbolos (letras, dígitos, símbolos especiales: +,*,&,$,#,!,', / ).Con estos símbolos se representan las construcciones del lenguaje tales como variables, etiquetas, palabras reservadas, constantes, operadores, etc. Es necesario que el traductor identifique los distintos significados de estas construcciones, que los creadores de lenguajes dan en la definición del lenguaje. El programa fuente se trata inicialmente con el analizador léxico (en inglés scanner) con el propósito de agrupar el texto en grupos de caracteres con entidad propia. llamados tokens, unidades sintácticas o componentes léxicos, tales como constantes, identificadores (de variables, de funciones, de procedimientos, de tipos, de clases), palabras reservadas, y operadores. Por razones de eficiencia, a cada token se le asocia un atributo (o más de uno) que se representa internamente por un código numérico o por un tipo enumerado. Por ejemplo a un identificador se le puede dar una representación interna de un 1, a una constante entera un 2, a un operador aritmético un 3,..., cada palabra reservada tiene su propio código. Así la siguiente sentencia de Pascal: IF cuenta = sueldo THEN jefe := justo ; el analizador léxico la separa en la siguiente secuencia de tokens:

y les asigna su atributo, habitualmente por medio de un código numérico cuyo significado se ha definido previamente.

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

Una herramienta para generar analizadores léxicos a partir de la definición de los componentes léxicos o tokens de un lenguaje es lex, originario inicialmente del sistema operativo UNIX, pero del que existen actualmente versiones prácticamente para todos los sistemas operativos, y algunas de ellas de dominio público FLEX, y otras desarrolladas por universidades.

Figura de La fase de análisis léxico se halla bajo el control del análisis sintáctico

FUNCIÓN Construir una secuencia de unidades significativas sintácticas llamadas token. Reducir el Trabajo al Analizador Sintáctico. DATOS DE ENTRADA Cadena de caracteres PROCESAMIENTO Construcción de Tokens, reconocer tokens correctos mediante reconocedor de patrones (autómatas) Elimina los espacios en blanco, comentarios, etc. Reconocimiento de un token, mediante autómata finito, usando tabla de símbolos (un token reconocido es el lexema).

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

ANÁLISIS SINTÁCTICO (Parser). El análisis jerárquico se denomina análisis sintáctico. Este implica agrupar los componentes léxicos del programa fuente en frases gramaticales que el compilador utiliza para sintetizar la salida. Por lo general, las frases gramaticales del programa del programa fuente se presentan mediante un árbol de análisis sintáctico como el que se muestra a continuación. El análisis sintáctico (en inglés parser) es un análisis a nivel de sentencias, y es mucho más complejo que el análisis léxico. Su función es tomar el programa fuente en forma de tokens, que recibe del analizador léxico y determinar la estructura de las sentencias del programa. Este proceso es análogo a determinar la estructura de una frase en Castellano, determinando quien es el sujeto, el predicado, el verbo y los complementos. El análisis sintáctico agrupa a los tokens en clases sintácticas (denominadas no terminales en la definición de la gramática), tales como expresiones, procedimientos,...El analizador sintáctico o parser obtiene un árbol sintáctico (u otra estructura equivalente) en la cual las hojas son los tokens y cualquier nodo, que no sea una hoja, representa un tipo de clase sintáctica. Por ejemplo el análisis sintáctico de la siguiente expresión: (A+B)*(C+D) con las reglas de la gramática que se presenta a continuación dará lugar al árbol sintáctico

La estructura de la gramática anterior refleja la prioridad de los operadores, así los operadores "+" y "-" tienen la prioridad más baja, mientras que "*" y "/" tienen una prioridad superior. Se evaluarán en primer lugar las constantes, variables y las expresiones entre paréntesis. Los árboles sintácticos se construyen con un conjunto de reglas conocidas como gramática, y que definen con total precisión el lenguaje fuente. Al proceso de reconocer la estructura del lenguaje fuente se le conoce con el nombre de análisis sintáctico (parsing). Hay

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

distintas clases de analizadores o reconocedores sintácticos, en general se clasifican en dos grandes grupos: analizadores sintácticos ascendentes y analizadores sintácticos descendentes.

Figura Árbol de análisis sintáctico

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

Figura de Análisis dirigido por Sintaxis

FUNCIÓN Determinar si un programa es correcto sintácticamente, mediante la construcciónde árboles sintácticos. PROCESAMIENTO Arbol sintáctico, organiza los tokens usando reglas recursivas conocidas como gramáticas de libre contexto (definidas formalmente por BNF).

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

ANÁLISIS SEMÁNTICO El analizador semántico detecta la validez semántica de las sentencias aceptadas por el analizador sintáctico. El analizador semántico suele trabajar simultáneamente al analizador sintáctico y en estrecha cooperación. Se entiende por semántica como el conjunto de reglas que especifican el significado de cualquier sentencia sintácticamente correcta y escrita en un determinado lenguaje. Las rutinas semánticas deben realizar la evaluación de los atributos de las gramáticas siguiendo las reglas semánticas asociadas a cada producción de la gramática Por ejemplo para una expresión como: (A+B)*(C+D) el analizador semántico debe determinar que acciones pueden realizar los operadores aritméticos (+,*) sobre las variables A, B, C y D. Así cuando el analizador sintáctico reconoce un operador, tal como " + " o " * ", llama a una rutina semántica que especifica la acción que puede llevar a cabo. Esta rutina puede comprobar que los dos operandos han sido declarados, y que tienen el mismo tipo. También puede comprobar si a los operandos se les ha asignado previamente algún valor. A continuación se muestra un método de la clase Semántica (escrito en lenguaje C++) que realiza comprobaciones de tipo de operadores aritméticos y del operador de asignación. Los tipos de datos que comprueba son entero (I), real (F) y carácter (C), devuelve el tipo del resultado. En caso de error devuelve el tipo error (E).

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

FUNCIÓN Verifica que no ocurran errores semánticos usando un verificador de tipos. PROCESAMIENTO Proceso de verificación, consultar la tabla de símbolos para encontrar información de un identificador y la información ligada a este.

INSTALACIÓN DE UBUNTU 7.04 EN MICROSOFT VIRTUAL PC 2007

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

INSTALACIÓN DE UBUNTU 7.04 EN MICROSOFT VIRTUAL PC 2007 La información contenida en este documento está sujeta a cambios sin previo aviso. En futuras ediciones de este documento se irán incorporando dichos cambios. Todos los productos citados son marcas registradas de sus respectivos propietarios. LICENCIA DE ESTE DOCUMENTO: Se permite la copia y distribución de la totalidad o parte de esta obra sin ánimo de lucro. Toda copia total o parcial deberá citar expresamente el nombre del autor, su sitio Web, su correo electrónico e incluir esta misma licencia, añadiendo, si es copia literal, la mención “copia literal”. Se autoriza la modificación y traducción de la obra sin ánimo de lucro siempre que se haga constar en la obra resultante de la modificación el nombre de la obra originaria, el nombre del autor, su sitio Web y su correo electrónico. La obra resultante también será libremente reproducida, distribuida, comunicada al público y transformada en términos similares a los expuestos en esta licencia.

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

ÍNDICE 1. INTRODUCCIÓN 1.1. ¿Qué es Microsoft Virtual PC 2007? 1.2. ¿Qué es Ubuntu 7.04? 2. ¿QUÉ NECESITAMOS? 2.1. Requisitos 2.2. Enlaces de interés 3. VIRTUALIZACIÓN DE UBUNTU 7.04 3.1. Asistente para nuevo equipo virtual 4. INSTALACIÓN DE UBUNTU 7.04 4.1. Ejecución del equipo virtual 4.2. Iniciar la instalación de Ubuntu 5. OTRAS CONFIGURACIONES DE UBUNTU 7.04 5.1. Establecer la contraseña del usuario “root” 5.2. Configuración del sonido 5.3. Cambiar el reloj del sistema de UTC a Local 1. INTRODUCCIÓN 1.1. ¿Qué es Microsoft Virtual PC 2007? Microsoft Virtual PC 2007 es una herramienta gratuita que nos permite utilizar equipos virtuales con sus respectivos sistemas operativos en nuestro propio equipo. Para ver más detalles de esta herramienta y/o poder descargarla, consulta el apartado 2.2 “Enlaces de interés”. 1.2. ¿Qué es Ubuntu 7.04? Ubuntu 7.04 es una distribución Linux con Gnome como entorno de escritorio. Para ver más detalles consulta el apartado 2.2 “Enlaces de interés”. 2. ¿QUÉ NECESITAMOS? 2.1. Requisitos Para la instalación de Ubuntu 7.04 en Microsoft Virtual PC 2007 necesitamos: Procesador Intel Pentium 4.

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

1 GB de memoria RAM. Recomendable 2 GB de memoria RAM. 3 GB de espacio libre en disco duro. Sistema operativo Microsoft Windows XP Professional. Microsoft Virtual PC 2007 Imagen CD-ISO de la distribución de Ubuntu 7.04 (ubuntu-7.04-desktop-i386.iso). Si no dispones de alguna de estas características, consulta los requisitos del sistema tanto de Microsoft Virtual PC 2007 como de Ubuntu 7.04 en sus sitios Web oficiales, respectivamente. 2.2. Enlaces de interés » Sitio Web oficial de Osc@rNET (mi sitio):

http://www.ofgsoftware.com » Sitio Web oficial de Microsoft Virtual PC 2007 (en inglés):

http://www.microsoft.com/windows/products/winfamily/virtualpc/default.mspx » Descarga de Microsoft Virtual PC 2007 (en español):

http://www.microsoft.com/downloads/details.aspx?displaylang=es&FamilyID=04d26402-3199-48a3-afa2-2dc0b40a73b6 » Sitio Web oficial de Ubuntu (en inglés):

http://www.ubuntu.com » Descarga de Ubuntu 7.04 (imagen CD-ISO):

http://ubuntu-releases.datahop.it/7.04/ubuntu-7.04-desktop-i386.iso 3. VIRTUALIZACIÓN DE UBUNTU 7.04 3.1. Asistente para nuevo equipo virtual Antes de proceder con la instalación necesitamos crear un nuevo equipo virtual. Desde la consola de Virtual PC hacemos clic sobre el botón “Nuevo…” para ejecutar el “Asistente para nuevo equipo virtual”:

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

A través del asistente confeccionamos paso a paso el equipo virtual que albergará la versión 7.04 de Ubuntu. Para comenzar hacemos clic sobre el botón “Siguiente >”:

Seleccionamos la opción “Crear un equipo virtual” y hacemos clic en el botón “Siguiente >” para continuar:

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

Damos un nombre al equipo virtual, por ejemplo “Ubuntu 7.04”. Por defecto se creará dentro de la carpeta “Mis equipos virtuales” en “Mis documentos” pero podemos elegir otra ubicación distinta. Para continuar hacemos clic en el botón “Siguiente >”:

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

Seleccionamos el sistema operativo “Otro” y hacemos clic en el botón “Siguiente >” para continuar:

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

Ahora ajustamos la memoria RAM del equipo virtual según nuestras posibilidades, es decir, dependiendo de la memoria RAM física que tenemos instala en nuestro equipo. Para la instalación de Ubuntu 7.04 se recomienda 192 MB de RAM pero, para aprovechar mejor toda su potencia es aconsejable 256 MB o mas. Para pasar al siguiente paso del asistente hacemos clic en el botón “Siguiente >”:

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

Seleccionamos la opción “Un nuevo disco duro virtual” para crear el disco duro del equipo virtual y hacemos clic en el botón “Siguiente >” para continuar:

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

Ahora ajustamos el tamaño del disco duro virtual según nuestras posibilidades, es decir, dependiendo del espacio libre en disco que tenemos disponible en nuestro equipo. Para la instalación de Ubuntu 7.04 se recomienda 3 GB de espacio libre en disco pero, 16 GB o más de espacio libre en disco es más que suficiente. Para pasar al siguiente paso del asistente hacemos clic en el botón “Siguiente >”:

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

Para crear el equipo virtual y terminar con el asistente hacemos clic en el botón “Finalizar”:

4. INSTALACIÓN DE UBUNTU 7.04 4.1. Ejecución del equipo virtual Desde la consola de Virtual PC seleccionamos el equipo virtual que hemos creado con el asistente y hacemos clic en el botón “Iniciar” para su ejecución:

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

Una vez iniciado el equipo virtual, seleccionamos la opción “Capturar imagen de ISO…” dentro del menú “CD”:

Aparece entonces el cuadro de diálogo “Seleccione la imagen de CD para capturar” donde seleccionamos la imagen CD-ISO de la distribución de Ubuntu 7.04 “ubuntu-7.04-desktop-i386.iso”.

4.2. Iniciar la instalación de Ubuntu Después de capturar la imagen ISO del CD de distribución de Ubuntu 7.04, el equipo arranca desde dicha imagen y aparece un menú con diversas opciones:

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

Desde esta pantalla tenemos que configurar diversos aspectos antes de proceder con la instalación, tales como, el lenguaje y la resolución de pantalla entre otros. Para cambiar el lenguaje al español (por defecto aparece en inglés), pulsamos la tecla “F2” y seleccionamos la opción “Español” del menú:

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

Después de seleccionar el lenguaje ya no es necesario especificar la configuración del teclado, debido a que ésta viene asociada al lenguaje elegido. Para cambiar la resolución de la pantalla pulsamos el botón “F4” y seleccionamos la opción “1024x768x16”:

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

Una vez configurados los parámetros de lenguaje y resolución de pantalla, seleccionamos la opción “Iniciar Ubuntu en modo gráfico seguro” y pulsamos la tecla “F6” para agregar una serie de parámetros adicionales a las opciones de arranque:

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

Tenemos que añadir a continuación los siguientes parámetros adicionales: i8042.noloop clock=pit Descripción de los parámetros: El parámetro “i8042.noloop” permite que el ratón PS/2 que emula Virtual PC 2007 funcione con Ubuntu 7.04. El parámetro “clock=pit” permite corregir ciertos desfases con el reloj interno del equipo virtual.

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

Una vez añadidos los parámetros adicionales pulsamos la tecla “ENTER” para comenzar la carga de Ubuntu. Comienza la carga de Ubuntu. Esto puede demonarse varios minutos:

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

Cuando esté cargado completamente el entorno gráfico de Ubuntu, ejecutamos el asistente de instalación haciendo doble clic sobre el icono “Instalar”:

Lo primero que tenemos que hacer es seleccionar el idioma “Español” que será el predeterminado para el sistema final.

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

Hacemos clic en el botón “Adelante” para continuar. Ahora seleccionamos la zona horaria, por ejemplo, la correspondiente a la ciudad de “Madrid”, España.

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

Hacemos clic en el botón “Adelante” para seguir. Para la distribución del teclado seleccionamos, por ejemplo, “Spain” (para el teclado en español).

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

Hacemos clic en el botón “Adelante” para continuar. Ahora se muestra la pantalla donde podemos configurar las particiones del disco a nuestro gusto. Por defecto, dejamos las opciones que nos proporciona el asistente y hacemos clic en el botón “Adelante” para seguir:

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

En la pantalla de “Migrar Documentos y Configuraciones” hacemos clic en el botón “Adelante” para continuar. Se trata de una instalación nueva así que no existen cuentas para importar.

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

Ahora especificamos nuestra cuenta de usuario, por ejemplo “administrador” y la contraseña “sistema”. Además debemos especificar el nombre del equipo, por ejemplo, “ubuntu-704”:

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

Hacemos clic en el botón “Adelante” para seguir. Para comenzar con la instalación hacemos clic en el botón “Install”. Este proceso puede tardar varios minutos dependiendo de las características de nuestro sistema.

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

Una vez finalizada la instalación de Ubuntu tenemos que reiniciar el equipo virtual, pero antes tenemos que liberar la imagen CD-ISO de la distribución de Ubuntu 7.04. Para ello, desde la barra de menús del equipo virtual seleccionamos la opción “Liberar ubuntu-7.04-desktop-i386.iso” del menú “CD”. A continuación, ya podemos reiniciar.

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

Después de reiniciar el equipo virtual tenemos que realizar una serie de ajustes antes de utilizar Ubuntu 7.04. Para ello, en la pantalla de arranque y cuando se visualice el mensaje “GRUB loading …” pulsamos la tecla “ESC” para acceder al menú de arranque:

En el menú de arranque seleccionamos la segunda opción “… (recovery mode)” que nos permite arrancar en modo de recuperación:

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

Una vez arrancado el sistema aparece la shell (#). Tenemos que modificar el archivo “menu.lst” que contiene los parámetros de arranque del gestor GRUB. Este archivo se encuentra en el directorio “/boot/grub” (es bueno realizar antes de modificar una copia de este archivo, de lo contrario, una mala modificación podría darnos problemas posteriores en el arranque): # cd /boot/grub # cp menu.lst menu.lst.original # nano menu.lst Localizamos dentro de este archivo el bloque de parámetros de arranque de la opción “Ubuntu, kernel 2.6.20-15-generic”, y en la línea “kernel” agregamos: i8042.noloop

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

Ahora guardamos el archivo y reiniciamos: # reboot Con esto ya tendremos nuestro sistema operativo Ubuntu 7.04 funcionando perfectamente en Virtual PC 2007. 5. OTRAS CONFIGURACIONES DE UBUNTU 7.04 5.1. Establecer la contraseña del usuario “root” Si queremos cambiar la contraseña del usuario “root” debemos ejecutar el siguiente comando desde la consola: $ sudo passwd root Nos pedirá la nueva contraseña y su confirmación para el usuario “root”. 5.2. Configuración del sonido Ya que Virtual PC emula la tarjeta de sonido SB16 (Sound Blaster), podemos configurar el sonido de nuestro equipo virtual para que funcione correctamente con Ubuntu. Para ello, tenemos que editar el archivo “/etc/modules” y agregar una línea que contenga la palabra “sb”. Esto lo

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

tenemos que hacer desde la consola del sistema. Para modificar este archivo ejecutamos el siguiente comando desde la consola: $ sudo nano /etc/modules 5.3. Cambiar el reloj del sistema de UTC a Local Para cambiar el reloj del sistema de UTC a Local tenemos que editar el archivo “rcS” que está ubicado en el directorio “/etc/default” y cambiar la línea “UTC=yes” por “UTC=no”. * Construcción de un analizador léxico con Lex/Flex

• Funciones del analizador léxico/morfológico

• Construcción de un analizador léxico/morfológico

• Funcionamiento básico de Lex/Flex

• El fichero de especificación Lex/Flex - Estructura del fichero - La sección de definiciones - La sección de reglas - La sección de funciones de usuario

• Primer ejemplo

- El fichero de especificación - Creación del ejecutable - Realización de pruebas

• Segundo ejemplo - Diseño del fichero de especificación - El fichero de especificación - Creación del ejecutable y realización de pruebas - Modificación del diseño

• Los patrones de Lex/Flex - Descripción - Metacaracteres - Cómo se identifican los patrones en la entrada

• Tercer ejemplo - Enunciado - Realización de pruebas

• Fichero de especificación Lex/Flex para el lenguaje ASPLE - Construcción - El orden de las reglas

• Ficheros de entrada/salida de Lex/Flex Funciones del analizador léxico/morfológico

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

El analizador léxico/morfológico de un compilador es el responsable de identificar en el fichero del programa fuente los “tokens” del lenguaje particular para el que ha sido construido el analizador.

Además de identificar los tokens también se encarga de otras tareas como:

- eliminar espacios en blanco (blancos, tabuladores y saltos de línea) - eliminar comentarios - detectar errores morfológicos (símbolo no permitido, identificador demasiado largo, etc)

Construcción de un analizador léxico/morfológico Un analizador morfológico se puede desarrollar con distintos métodos:

- con un autómata finito - con un programa a medida - utilizando una herramienta específica como Lex/Flex

Independientemente del método seleccionado, para construir el analizador morfológico, hay que definir los requisitos que debe cumplir el analizador:

- los tokens que tiene que reconocer: - identificadores - palabras reservadas - constantes (numéricas, booleanas, literales, etc) - símbolos simples (+, -, *. /, etc) - símbolos múltiples (+=, >=, etc)

el formato de los comentarios los errores morfológicos que debe detectar Funcionamiento básico de Lex/Flex Lex/Flex es una herramienta para construir analizadores léxicos. Lex/Flex recibe como entrada un conjunto de descripciones de tokens, y genera la función C yylex() que es un analizador léxico que reconoce dichos tokens. Los tokens se describen mediante patrones que son extensiones de las expresiones regulares. Al conjunto de las descripciones de tokens se le llama especificación lex/flex.

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

El fichero de especificación Lex/Flex (I) Estructura del fichero La estructura del fichero de especificación Lex/Flex se compone de tres secciones separadas por líneas que contienen el separador %%

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

. El fichero de especificación Lex/Flex (II) La sección de definiciones (i) La sección de definiciones contiene la siguiente información

- Código C encerrado entre líneas con los caracteres %{ y %} que se copia literalmente en el fichero de salida lex.yy.c antes de la definición de la función yylex(). Habitualmente esta sección contiene declaraciones de variables y funciones que se utilizan posteriormente en la sección de reglas así como directivas

#include. - Definiciones propias de Lex/Flex, que permiten asignar un nombre a una expresión

regular o a una parte, y posteriormente utilizar ese nombre en la sección de reglas. Estas definiciones se verán con más detalle cuando se estudien los patrones de

Lex/Flex. - Opciones de Lex/Flex similares a las opciones de la línea de comandos.

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

- Definición de condiciones de inicio. Estas definiciones se verán con más detalle cuando se estudien los patrones de Lex/Flex.

- Cualquier línea que empiece con un espacio en blanco se copia literalmente en el fichero de salida lex.yy.c. Habitualmente se utiliza para incluir comentarios encerrados entre /* y */.

Sección de definiciones del primer ejemplo de Lex/Flex

- El primer ejemplo de utilización de Lex/flex que se va a construir es un analizador léxico que reconoce en la entrada las palabras clave “begin” y “end”, y que cada vez que encuentra una de ellas muestra en la salida estándar un mensaje de aviso de token reconocido.

- El fichero de especificación Lex/Flex se llamará ej1.l, y en la figura se muestra una primera aproximación del mismo en la que únicamente se ha completado la sección de definiciones. A medida que se construya el ejemplo se completarán las demás secciones.

- La directiva #include <stdio.h> se necesita porque en la sección de reglas se utiliza la función printf para mostrar en la salida estándar los mensajes de aviso de token reconocido.

- El significado de la opción noyywrap se explica en el siguiente apartado.

Significado de la opción noyywrap:

- Existe la posibilidad de que la función yylex() analice morfológicamente varios ficheros, encadenando uno detrás de otro con el siguiente mecanismo.

- Cuando yylex() encuentra un fin de fichero, llama a la función yywrap(). Si la función devuelve 0, el análisis continúa con otro fichero, y si devuelve 1, el análisis termina.

- ¿Quién proporciona la función yywrap()? - En Linux, la librería de lex proporciona una versión por defecto de yywrap() que devuelve 1. Hay que enlazar con esa librería.

- En Windows la tiene que proporcionar el usuario incorporándola en la última sección del fichero de especificación. En Linux también la puede proporcionar el usuario.

- La opción noyywrap provoca que no se invoque a la función yywrap() cuando se encuentre un fin de fichero, y se asuma que no hay más ficheros que analizar.

Esta solución es más cómoda que tener que escribir la función o bien enlazar con alguna librería.

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

La sección de reglas contiene:

- Los patrones que describen los tokens y código C. Cada patrón se sitúa en una línea,

seguido de uno o más espacios en blanco y a continuación el código C que se ejecuta cuando se encuentra dicho patrón en la entrada que se está analizando. El código C se cierra entre llaves { }.

- Si una línea empieza por un espacio en blanco se considera código C y se copia literamente en el fichero de salida. También se asume´que es código C todo lo que se escriba entre %{ y %}, y se copia literalmente en el fichero de salida.

¿Cómo se comporta la rutina de análisis yylex()? - Busca en la entrada los patrones que se definen en la sección de reglas, es decir,los

tokens. Cada vez que se encuentra un token, se ejecuta el código C asociado con el patrón. Si no se identifica ningún token, la regla por defecto se ejecuta: el siguiente caracter en la entrada se considera reconocido y se copia en la salida.

- El código C asociado a cada patrón puede tener una sentencia return, que devuelve un valor al llamador de la función yylex()cuando se identifique el patrón en la entrada. La siguiente llamada a yylex() comienza a leer la entrada en el punto donde se quedó la última vez. Cuando se encuentra el fin de la entrada yylex() devuelve 0 y termina.

- Si ninguna regla tiene una sentencia return, yylex() analiza la entrada hasta que encuentra el fin de la misma.

Sección de reglas del primer ejemplo:

- Los patrones de Lex/Flex para definir los tokens se estudiarán más adelante, y ya se verá que el patrón para representar una palabra clave es la misma palabra clave.

- La sección de reglas del primer ejemplo contiene: un patrón para la palabra clave “begin”, que es la misma palabra, y el código C que se va a ejecutar cuando se identifique en la entrada. El código muestra en la salida estándar el aviso “reconocido-begin-”.

- Un patrón para la palabra clave “end”, que es la misma palabra, y su código C asociado que muestra en la salida estándar el aviso “reconocido-end-”.

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

La sección de funciones de usuario La sección de funciones de usuario se copia literalmente en el fichero de salida. Esta sección habitualmente contiene las funciones escritas por el usuario para ser utilizadas en la sección de reglas, es decir, funciones de soporte. En esta sección también se incluyen las funciones de Lex/Flex que el usuario puede redefinir, por ejemplo la función yywrap() se situaría en esta sección. La función yylex() generada por Lex/Flex, tiene que ser invocada desde algún punto, habitualmente desde el analizador sintáctico. Pero para realizar pruebas de la función yylex() se incorpora una función main en la sección de funciones de usuario dentro del fichero de especificación Lex/Flex. Una versión muy simple sería: int main() { return yylex(); } Esta llamada única a yylex() permite realizar el análisis léxico hasta encontrar el fin de la entrada siempre que en ningún fragmento de código C asociado a los patrones de los tokens aparezca una sentencia return que haga terminar a yylex(). En ese caso el main sería distinto como se mostrará mas adelante. El fichero de especificación ej1.l

Se completa el fichero ej1.l incluyendo una función main en la sección de funciones de usuario.

Creación del ejecutable

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

A partir del fichero de especificación Lex/Flex ej1.l se genera el ejecutable de la siguiente manera: - Compilar la especificación Lex/Flex

flex ej1.l se crea el fichero lex.yy.c - Generar el ejecutable:

Linux: gcc -Wall -o ej1 lex.yy.c se crea el fichero ej1 Windows: cl /W4 /Feej1.exe lex.yy.c se crea el fichero ej1.exe Realización de pruebas

Para probar el funcionamiento del analizador léxico implementado en el primer ejemplo, se arranca el ejecutable y se realizan las siguientes pruebas:

Diseño del fichero de especificación

En el primer ejemplo, la función main invoca a la función yylex() una sola vez, y como en ninguna de las reglas aparece una sentencia return, la función yylex() se ejecuta hasta encontrar el fin de la entrada. En el segundo ejemplo, se quiere modificar la especificación Lex/Flex para que la función yylex() devuelva un valor diferente para cada token identificado, y la función main sea la responsable de mostrar por la salida estándar el aviso correspondiente del token identificado. Los cambios que hay que hacer son los siguientes:

- definir constantes diferentes para los tokens. En la sección de definiciones se añaden las siguientes sentencias:

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

#define TOK_BEGIN 1 #define TOK_END 2

- modificar la sección de reglas para que el código C asociado a cada token en lugar de mostrar en la salida estándar un aviso, devuelva la constante definida para el token.

begin { return TOK_BEGIN; } end { return TOK_END; } Modificar la función main para que llame a la función yylex() y muestre un mensaje de aviso diferente para cada token en función del valor devuelto por yylex(). La función main llama repetidas veces a la función yylex() hasta que se termina la entrada, es decir, hasta que la función yylex() devuelve 0. int main() { int token; while (1) { token = yylex(); if (token == TOK_BEGIN) printf(“reconocido-begin-\n”); if (token == TOK_END) printf(“reconocido-end-\n”); if (token == 0) break; } return 0; } El fichero de especificación ej2.l

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

Creación del ejecutable y realización de pruebas

A partir del fichero de especificación Lex/Flex ej2.l se genera el ejecutable:

- Compilar la especificación Lex/Flex flex ej2.l se crea el fichero lex.yy.c

- Generar el ejecutable: Linux: gcc -Wall -o ej2 lex.yy.c se crea el fichero ej2 Windows: cl /W4 /Feej2.exe lex.yy.c se crea el fichero ej2.exe Si se realizan las mismas pruebas que se hicieron con ej1.exe se obtendrán los mismos resultados ya que no se ha modificado la funcionalidad del analizador léxico sino su diseño.

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

Modificación del diseño

La definición de los tokens se traspasa a un fichero de cabecera tokens_ej2.h, y se incluye dicho fichero en el fichero de especificación ej2.l.

Los patrones de Lex/Flex Los patrones de Lex/Flex son:

- el mecanismo para representar los tokens. - una extensión de las expresiones regulares.

Los patrones están formados por: - caracteres “normales” que se representan a sí mismos - metacaracteres que tienen un significado especial

Para utilizar un metacaracter como caracter “normal” hay que ponerlo entre comillas. Por ejemplo, el asterisco es un metacaracter, y si se quiere reconocer el token asterisco, hay que definirlo como “*”. En los siguientes apartados se describen algunos de los metacaracteres de Lex/Flex Un mismo token se puede expresar con distintos patrones. Metacaracteres

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

Representa cualquier caracter exceptuando el salto de línea "\n". [ ] Representa cualquiera de los caracteres que aparecen dentro de los corchetes. Para indicar un rango de caracteres se utiliza el símbolo menos "-".

• [xyz] representa una "x", una "y" o una "z"

• [abj-oZ] representa una "a", una "b" cualquier letra de la "j" a la "o", o una "Z"

• [ \t] representa el espacio y el tabulador

• [0-9] representa los dígitos del 0 al 9

• [a-z] representa las letras minúsculas

• [A-Z] representa las letras mayúsculas

• [a-zA-Z] representa las letras minúsculas y las mayúsculas

• [0-9a-zA-Z] representa los dígitos del 0 al 9, las letras minúsculas y las Mayúsculas * Indica 0 ó más ocurrencias de la expresión que le precede.

- ab* representa todas las palabras que empiezan por una "a" seguida de 0 o más "b", por ejemplo "a", "ab", "abb", "abbb".

- [a-zA-Z][a-zA-Z0-9]* representa todas las palabras que empiezan por una letra minúscula o mayúscula seguida de 0 o más letras o dígitos, como por ejemplo "v1", "indice", "maximo", "D".

+ Indica 1 ó más ocurrencias de la expresión que le precede.

- x+ representa todas las palabras formadas por "x", por ejemplo "x", "xx", "xxx". [0-9]+ representa los números de uno o más dígitos, por ejemplo "12", "465", "000", "097". | Identifica la expresión que le precede o la que le sigue.

- A|B representa la letra "A" o la letra "B". Este patrón se comporta de la misma manera que el patrón [AB]. "..." Representa lo que esté entre las comillas literalmente. Los metacaracteres pierden su significado excepto "\". El metacaracter \ si va seguido de una letra minúscula se asume que es una secuencia de escape de C, como por ejemplo el tabulador \t. Si no va seguido de minúscula, \ se utiliza para quitar el significado especial de los metacaracteres, por ejemplo \* representa un asterisco.

- "/*" representa a la agrupación de caracteres "/*". ( ) Agrupan expresiones.

- (ab|cd)+r representa "abr", "ababr", "cdr", "cdcdr", “abcdr”, etc. { nombre } Un nombre encerrado entre llaves significa al expansión de la definición de "nombre". En la sección de definiciones se pueden definir expresiones regulares y

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

asignarles un nombre. Por ejemplo: DIGITO [0-9] LETRA [a-zA-Z] Posteriormente, cualquier aparición de {DIGITO} se sustituye por la expresión regular [0-9], y {LETRA} por [a-zA-Z]. La expresión regular de todas las palabras que empiezan por una letra minúscula o mayúscula seguida de 0 o más letras o dígitos: [a-zA-Z]([0-9]|[a-zA-Z])* Cómo se identifican los patrones en la entrada

El analizador busca en la entrada cadenas de caracteres que concuerden con alguno de los patrones.

- Si se encuentra concordancia con más de un patrón, se selecciona el patrón más largo. Por ejemplo, si se definen las reglas: begin { return TOK_BEGIN; } end { return TOK_END; } [a-z]+ { return TOK_ID;} La entrada beginend se identificará como TOK_ID. Durante el análisis de la entrada, concuerdan dos patrones, el de TOK_BEGIN y el de TOK_ID, hasta que se lee la segunda “e”, en ese momento se descarta el patrón correspondiente a TOK_BEGIN, y se selecciona el patrón de TOK_ID que es más largo. (Se recomienda construir un analizador léxico de prueba) Si hay concordancia con varios patrones, y además de la misma longitud, se elige el patrón que esté situado primero en la sección de reglas dentro del fichero de especificación Lex/Flex. Por lo tanto, es determinante el orden en que se colocan las reglas. Por ejemplo, si se definen las reglas: [a-z]+ { return TOK_ID;} begin { return TOK_BEGIN; } end { return TOK_END; } La entrada begin se identificará como TOK_ID porque hay concordancia con dos patrones, el de TOK_BEGIN y el de TOK_ID, pero el patrón de TOK_ID aparece antes en el fichero de especificación. Al procesar con Lex/Flex estas reglas, se muestra un mensaje en el que se indica que las reglas segunda y tercera nunca se van a identificar. (Se recomienda construir un analizador léxico de prueba) En el ejemplo anterior, para que se identifiquen las palabras reservadas begin y end correctamente, y no como identificadores, se deben colocar sus correspondientes reglas antes de la regla de los identificadores, de la siguiente manera:

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

begin { return TOK_BEGIN; } end { return TOK_END; } [a-z]+ { return TOK_ID;} Cuando se ha determinado el patrón que concuerda con la entrada, el texto del token se almacena en la variable interna de Lex/Flex yytext que es un char*, y la longitud se almacena en la variable entera yyleng. Enunciado

Diseñar un fichero de especificación Lex/Flex, ej3.l, usando como guía el fichero de especificación del segundo ejemplo, para construir un analizador léxico que se ajuste a los siguientes requisitos. Los tokens que debe reconocer el analizador léxico son:

- TOK_ID: Palabra formada por letras mayúsculas o minúsculas y dígitos del 0 al 9, y cuyo carácter inicial es una letra.

- TOK_NUM: palabra formada por dígitos del 0 al 9. - La definición de los tokens se realiza en el fichero tokens_ej3.h.

Las acciones que debe realizar el analizador léxico cuando reconozca alugún token son las siguientes:

- Cuando se reconozca un identificador se mostrará el mensaje “TOK_ID” - Cuando se reconozca un número se mostrará el mensaje “TOK_NUM”.

Compilar la especificación Lex y generar el ejecutable. Realización de pruebas

Tutorías de Compiladores - Instalación UBUNTU

Ingeniería de Sistemas

Ficheros de entrada/salida de Lex/Flex

FILE* yyin

- es el fichero de entrada, del que lee la función yylex(). - Por defecto es la entrada estándar. - El usuario puede asignarle cualquier variable de tipo FILE*, por supuesto, antes de que se

inicie el análisis, es decir, antes de invocar a la función yylex(). FILE* yyout

- es el fichero de salida, en el que se escribe la regla por defecto (cuando no concuerda ningún patrón se copia la entrada en la salida)

- Por defecto es la salida estándar. - El usuario puede asignarle cualquier variable de tipo FILE*.