Clase #6 - Laboratorio

Clase #6 - Laboratorio - Compiladores Compiladores e...

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 JLex: Herramienta para la generación automática de analizadores léxicos Luis Ochoa ziul1979@gmail.com JLex Se encuentra disponible en: http://www.cs.princeton.edu/~appel/modern/java/JLex/ www.jflex.de (otra version más moderna casi similar pero con ciertas diferencias) ¿Qué es JLex? Es una generador de analizadores léxicos (scanner generator) para el lenguaje Java, escrito en el lenguaje Java. Es igualmente una nueva versión del generador Lex, aunque no comparten código en común. Introducción • Conceptualmente el esquema básico de un compilador se agrupa en dos bloques o fases: ▫ Fase de análisis o front-end, en la cual se realiza el análisis léxico, sintáctico y semántico del programa fuente. ▫ Fase de síntesis o back-end, en la cual se genera y optimiza el código objeto. Introducción En el año 1975 nace el generador de analizadores léxicos LEX y el generador de analizadores sintácticos YACC, utilizados por el sistema UNIX. • El analizador léxico es el único bloque funcional del compilador que lee el archivo fuente. Su misión es agrupar los caracteres que lee del archivo fuente, formando los lexemas del lenguaje, y transmitir al analizador analizador sintáctico el significado de esos lexemas, que llamamos testigos. • La programación de esta primera fase de los compiladores era una tarea pesada que representaba una parte importante del tiempo dedicado a la implementación de un compilador. A partir de los artículos publicados por M.E. Lesk y Stephen C. Johnson en el año 1975, esta fase puede generarse de forma automatizada: ▫ A partir de la especificación léxica del lenguaje, realizada mediante expresiones regulares, se puede automatizar la creación y simplificación de un autómata finito que represente cada una de éstas. El conjunto de autómatas se pueden combinar en una única máquina que represente la totalidad del lenguaje. Introducción • Así, podemos representar la construcción de un compilador, utilizando esta herramienta, mediante mediante el gráfico siguiente: JLex: Nociones generales • JLex es un generador automático de analizadores léxicos, realizado en Java, y en que el código del explorador generado es, también, Java. • El compilador JLex traduce un archivo fuente, escrito en lenguaje JLex, a un archivo objeto, en lenguaje Java, que implementa el explorador (analizador léxico) del lenguaje descrito en el archivo fuente. • El nombre del archivo generado es, por defecto, el del archivo fuente con la extensión añadida ‘.java’. El nombre de la clase generada es, por defecto, Yylex. JLex: Nociones generales • La secuencia de comandos genérica para crear el explorador es: 1. Generación del explorador (Jlex -> Java): java JLex.Main explora.lex 2. Compilación del explorador (Java -> Byte-Code): javac –d . explora.lex.java JLex: Nociones generales • El diagrama de Tombstone que representa la creación de un explorador utilizando JLex sobre un ordenador Pentium se presenta a continuación. Estructura de un archivo JLex • Un archivo de especificación léxica de JLex se organiza en tres secciones, separadas por directivas directivas de tanto por ciento doble (“%%”): Código de usuario %% Directivas de JLex %% Reglas de expresiones regulares Estructura de un archivo JLex • Las directivas “%%” tienen que ser los primeros caracteres de la línea. • La sección de código de usuario, la primera sección del archivo de especificación, se copia directamente en el explorador generado. Esta sección se utiliza para incluir sentencias Java de importación y la definición de clases y tipos de datos que puedan ser de interés para la aplicación que que queremos generar. • La sección de directivas se utiliza para particularizar algunas características del explorador generado y, también, es donde se declaran las macros y estados que se usarán en la definición de las reglas léxicas. • La sección de reglas léxicas, como su nombre indica, contiene las reglas léxicas que se utilizarán para generar el explorador. El programa JLex más corto que puede compilarse es: %% %% El código generado • El explorador generado por JLex a partir del archivo de especificación contiene código que es generado generado automáticamente y código Java que, opcionalmente, incluimos en las tres secciones. • El explorador se implementa con una clase que, por defecto, tiene el nombre Yylex. Además, el archivo generado incluirá todas las clases adicionales que se describan en la sección de código de usuario (sección 1). El código generado • El resto de código definido por el usuario que se incluye en el explorador es: 1.) Definido en la sección de directivas. ▫ Código interno a la clase Yylex. ▫ Código de inicialización de la clase Yylex y la definición de las excepciones que este código puede lanzar. ▫ Código que se desea ejecutar al finalizar la lectura del archivo a explorar y la definición de las excepciones que este código puede lanzar. El código generado 2.) Definido en la sección de reglas léxicas. • La sección de reglas léxicas se estructura en una serie de expresiones regulares, para cada una de las cuales se puede definir un código (o acción léxica) que se ejecuta cuando los caracteres leídos por el explorador encajan con esta regla. Este código se incorpora al explorador generado. • Igualmente, el código contenido en las acciones léxicas puede lanzar excepciones que se pueden declarar en la sección de directivas. El código generado • El esquema siguiente refleja la inclusión del código de usuario –en negrita– en el código del explorador generado por JLex. Actividad #1 • Utiliza un editor de texto para crear el “programa mínimo”, minimo.lex, y generar el código código del explorador. ▫ ¿Qué comentarios puedes hacer del código generado? Aplicaciones standalone • La clase generada por JLex incluye la función yylex( ) que es la que implementa el autómata de la gramática. Es decir, es la función que identifica la regla léxica o patrón que encaja con la entrada actual. Esta función retorna el testigo correspondiente al lexema identificado que, por defecto, es de tipo Yytoken. • Esto corresponde plenamente con el esquema inicial sobre el funcionamiento de un compilador: el explorador lee la entrada, identifica el lexema que pertenece a la gramática y entrega a la fase siguiente del compilador (el analizador sintáctico) el testigo correspondiente. Esta secuencia se repite bajo el control de las fases siguientes al análisis léxico que, normalmente, dejarán de solicitar testigos si se llega al final del archivo de entrada o si se produce un error irrecuperable en el análisis. Aplicaciones standalone • Por tanto, siguiendo esta lógica, el explorador generado por JLex no proporciona ninguna función para controlar su propio proceso de análisis. Para poder implementar programas-exploradores que funcionen de forma no subordinada a un analizador sintáctico, es necesario proporcionar a la clase Yylex una una función que gestione las peticiones reiteradas de testigos a la función yylex(). • Sin entrar en mayores detalles, propondremos una rutina para gestionar la petición de testigos a la función yylex( ), considerando que el archivo de entrada es el dispositivo por defecto del sistema (System.in). Aplicaciones standalone • Como puede deducirse de su lectura, únicamente consiste en la función main(...), con un bucle en que se llama a la función yylex yylex() y que se repite hasta que ésta nos retorna el valor (-1), correspondiente al testigo que por defecto representa el fin del archivo de entrada. • Actividad 2: Modifica esta función main para que lea de un archivo cuyo nombre se pase como parámetro en java. Repasar los conceptos del sistema de entrada de Java (Presentación con esta información y MÁS en página web de la materia)111 Sección de reglas léxicas • La tercera sección del archivo de especificación contiene las reglas que nos permitirán convertir el archivo de entrada en una secuencia de testigos. Cada una de éstas se estructura en tres partes: 1. Una lista opcional de estados. 2. Una expresión regular. 3. Una acción léxica. Sección de reglas léxicas • Las expresiones regulares deben estar delimitadas por la derecha por un espacio en blanco; este carácter marca la finalización de la regla léxica. • La acción léxica es código Java que introduciremos para que se ejecute cuando una entrada encaja con la regla léxica a la que precede. • Los estados se explicarán en un apartado posterior. Sección de reglas léxicas: Expresiones regulares En JLex, las expresiones regulares se construyen siguiendo las reglas siguientes: • Todo carácter del lenguaje es una expresión regular que se representa a sí mismo. Por ejemplo, la expresión regular que contiene, únicamente, el carácter ‘a’: Observa que los caracteres ‘a’ y ‘A’ son, de hecho, caracteres a [Acción_Léxica] diferentes. encajará con cualquier carácter del archivo que sea una ‘a’ y, cuando este carácter aparezca en el archivo, se ejecutará la acción léxica asociada. Sección de reglas léxicas: Expresiones regulares • Las excepciones a esta regla son: Los metacaracteres son caracteres que tienen un significado especial para JLex. ▫ El espacio blanco, debido a que es el carácter utilizado como delimitador de final de la expresión. ▫ El conjunto de metacaracteres de JLex: ?*+|()^${}“\. • Para incluir estos metacaracteres como caracteres convencionales, se se dispone de tres alternativas: ▫ Delimitarlos con comillas: “*” representa el carácter ‘asterisco’. “ “ representa el espacio en blanco. Por extensión, cualquier carácter delimitado por comillas representa al carácter contenido. “a” representa el carácter ‘a’. Sección de reglas léxicas: Expresiones regulares ▫ Anteponer la barra invertida: \* representa el carácter ‘asterisco’. \\ representa el carácter ‘barra invertida’ Por extensión, cualquier carácter precedido por la barra invertida representa al propio carácter: \a representa el carácter ‘a’. Sección de reglas léxicas: Expresiones regulares ▫ Referirse a ellos por su código de carácter, de acuerdo con las alternativas siguientes: \ooo, La barra invertida seguida del código del carácter, expresado en formato octal (3 dígitos ‘ooo’). \xhh La barra invertida, seguida del carácter x y del código del carácter, expresado en formato hexadecimal (2 dígitos ‘hh’). \uhhhh La barra invertida, seguida del carácter u y del código del carácter unicode, expresado en formato hexadecimal (4 dígitos ‘hhhh’). Sección de reglas léxicas: Expresiones regulares • Un caso particular de caracteres son los caracteres de control o secuencias de escape. Para incluirlas en las reglas léxicas, JLex reconoce los casos siguientes: ▫ ▫ ▫ ▫ ▫ ▫ \b Retroceso. \n Nueva línea. \t Tabulación. \f Avance de línea. \r Retorno de carro. \^C Carácter de control. Sección de reglas léxicas: Expresiones regulares • El carácter dólar ($) denota el extremo de una línea. Si se sitúa en el extremo de una expresión regular, ésta sólo encajará si viene seguida por un final de línea. • El carácter (^) denota el inicio de una línea. Si se situa al principio de una expresión regular, ésta sólo encajará si se encuentra al principio de la línea. Sección de reglas léxicas: Expresiones regulares • Dos expresiones regulares consecutivas representan la concatenación de estas: ▫ ab encajará todo lexema formado por la concatenación de ‘a’ y ‘b’; es decir, el lexema “ab”. ▫ 3\n encajará el carácter ‘3’ seguido de un retorno de carro. Actividad 3 ¿Qué lexema cres que encajará con la regla \x41323 ? a) El carácter ‘A’, que tiene el código hexadecimal 41, seguido de los números 323. b) El carácter ‘x’, seguido de los números 41323. Compruebalo con un programa JLex. Sección de reglas léxicas: Expresiones regulares • Dos expresiones regulares separadas por la barra vertical ( | ), representa la opción entre estas dos o ‘unión’: Nota que del segundo ejemplo se A|B encaja el carácter ‘A’ o el carácter ‘B’. gato|perro encaja ‘gato’ o ‘perro’. (No encaja ni ‘gaperro’ ni ‘peto’). deduce que la concatenación tiene mayor precedencia que la unión. (|). • Los paréntesis se utilizan para agrupar expresiones regulares; no alteran el significado de la expresión que contienen y, como en las operaciones aritméticas, los podemos utilizar para modificar la precedencia: agu(a|v)ino encaja ‘aguaino’ o ‘aguvino’. (No encaja ni ‘agua’ ni ‘vino’). • El asterisco (*) representa la clausura de Kleene, es decir, la repetición de cero o más veces la expresión regular que la precede. agua* encaja ‘agu’, agua, aguaa, etcétera. Observa que la clausura de Kleene tiene mayor precedencia que la concatenación. Sección de reglas léxicas: Expresiones regulares • Como ya se ha comentado, la precedencia puede modificarse mediante el uso de paréntesis: (agua)* encaja ‘agua’, aguaagua, aguaaguaagua, etcétera. • El signo suma (+) encaja con una o más repeticiones de la expresión regular que lo precede. Si expr es una expresión regular, entonces expr+ es equivalente a exprexpr*. • El interrogante (?) encaja una o ninguna repetición de la expresión regular que lo precede. ▫ Los metacaracteres *, + y ? no pueden encadenarse entre sí. Por ejemplo, las expresiones siguientes son ilegales: gato+*, gato*?, gato?+ Sección de reglas léxicas: Expresiones regulares • El punto (.) encaja con cualquier carácter excepto el de nueva línea. • La expresión {nombre} denota una expansión de macro. Las macros se declaran en la sección de directivas. directivas. • Los corchetes ([...]) denotan una clase de caracteres y encajan con cualquiera de los caracteres que contienen. Por ejemplo, la regla [abc] encajaria con la letra ‘a’, la ‘b’ o la ‘c’. Sección de reglas léxicas: Expresiones regulares • No todos los metacaracteres explicados hasta el momento mantienen su significado dentro de una clase. Concretamente: ▫ El guión (-) se utiliza para definir un rango de caracteres dentro de la clase. Por ejemplo, [a-z] encaja con cualquier letra minúscula. ▫ El circumflejo (^), situado después de la apertura del corchete indica la negación de la clase. Por ejemplo: [^abc] encaja con cualquier carácter que no sea ‘a’, ni ‘b’, ni ‘c’. [^a-z] encaja con cualquier carácter que no sea una letraminúscula. ▫ Los metacaracteres delimitados por comillas ("...") pierden su significado especial. ▫ Los metacaracteres precedidos por la barra invertida (\) pierden su significado especial. Por ejemplo, la secuencia \” representa el carácter de una comilla (“). ▫ Se permite el uso de macros en el interior de una clase. Sección de reglas léxicas: Expresiones regulares Ejemplo Algunos ejemplos que podrían inducir a error: Si el explorador generado recibe una entrada que no encaja en ninguna regla, se genera un error. Por tanto, las reglas que se implementen en la especificación han de encajar con todas las entradas posibles, no únicamente aquéllas que forman parte del lenguaje. Esto puede conseguirse con la inclusión de la regla siguiente en la última posición del archivo. .{} El motivo por el que esta regla debe ser la última se explica en el apartado Ambigüedad. Los errores más habituales en los programadores noveles se derivan del uso incorrecto de metacaracteres en el interior de las clases. Sección de reglas léxicas: Expresiones regulares • En la tabla siguiente se detallan los distintos significados de los metacaracteres, en función de si se encuentran en el interior de una clase de caracteres o fuera de ésta (en una expresión regular). Sección de reglas léxicas: Expresiones regulares Actividad 4 • Indica las expresiones regulares correspondientes: Ambigüedad • En una especificación léxica, pueden darse las siguientes situaciones: a) Dos o más reglas encajan con una misma entrada o lexema. b) Una regla encaja con un lexema y una segunda regla encaja con un fragmento de este lexema. • El explorador generado por JLex resuelve esta ambigüedad aplicando los siguientes criterios: 1) Si más de una regla encaja con un fragmento de la entrada, se elige una regla que encaja con la cadena más larga. 2) Si más de una regla encaja con la misma cadena, se elige la regla que se encuentra en primera posición de entre ellas. Ambigüedad Acciones léxicas • Las acciones léxicas consisten en código Java delimitado por llaves: { Código_Java } • Este código es copiado íntegramente por JLex dentro dentro del explorador y concretamente en la función yylex(), y se ejecutará cuando la entrada encaje con el patrón correspondiente. Acciones léxicas: Valores léxicos El código contenido dentro de las acciones léxicas puede acceder a la siguiente serie de valores que proporciona JLex en el explorador generado: • yytext(): Función que nos retorna el fragmento de entrada identificado. La función siempre está disponible y el tipo de valor retornado es java.lang.String. • yychar: Entero, de tipo int, que contiene la posición, dentro el archivo de entrada, del primer carácter del lexema actual. Para tener disponible este valor, hay que definir la directiva de activación dentro de la sección de directivas JLex. Acciones léxicas: Valores léxicos • yyline: Entero, de tipo int, que contiene el número de línea donde se encuentra, dentro del archivo de entrada, el primer carácter del lexema actual. Para tener disponible este valor, hay que definir la directiva de activación dentro de la sección sección de directivas Jlex. Actividad 5 Crea un explorador que identifique y muestre por la salida estándar todos los dígitos decimales contenidos en un archivo de texto cualquiera. Acciones léxicas: Recursividad • Las acciones léxicas pueden incluir la sentencia return(...) para que la función yylex(...) retorne el valor o testigo que deseamos a la función que la ha invocado (en un compilador, el analizador sintáctico). • Si la acción no contiene esta sentencia de retorno, el explorador reiniciará la búsqueda de un nuevo testigo. • Como toda función de Java, yylex() puede contener llamadas recursivas que, en nuestro caso, se activen desde las acciones léxicas. Si la llamada es recursiva por el final, es equivalente al caso anterior. Acciones léxicas: Recursividad Los dígitos 0, 1, 2 y 3 encajan con la segunda expresión regular y su acción asociada no contiene código. El resultado es que estos dígitos no se imprimirán nunca; después de encajar estos lexemas, el método yylex() iniciará una nueva búsqueda sin retornar ningún valor al método main(). Los dígitos 4, 5 y 6 encajan con la tercera expresión regular y su acción asociada consiste en una llamada a la función yylex(). El resultado es que estos dígitos no se imprimirán nunca; después de encajar estos lexemas iniciará otra búsqueda sin devolver ningún testigo correspondiente a esta. Los dígitos 7, 8 y 9 encajan con la tercera expresión regular y su acción asociada consiste en devolver el valor entero correspondiente. El resultado es que el método main() recibirá este valor y lo imprimirá por la salida estándar. Estados • En la especificación léxica de un lenguaje nos podemos encontrar con que varios de sus componentes tienen diferentes significados o interpretaciones en función del lugar donde aparezcan dentro del archivo de entrada. • Por ejemplo, consideremos el siguiente fragmento de programa en C: int var1; /* var1 */ • En este fragmento, se observa que la primera ocurrencia de la cadena var1 corresponde a la declaración de una variable entera. En este caso, el analizador léxico encajará –por ejemplo– con el patrón [a-zA-Z][a-zA-Z09_]*, y ejecutará su acción asociada (ponerlo en la tabla de símbolos, retornar al analizador sintáctico el testigo correspondiente,…). Estados • La segunda ocurrencia de var1 también encaja con el mismo patrón, pero el analizador léxico tiene que prescindir de él por el hecho de encontrarse dentro de un comentario. En caso contrario, la secuencia de testigos que recibiría el analizador sintáctico podría ser errónea. • Esto se implementa mediante los estados léxicos. • El explorador generado por JLex dispone de un estado implícitamente declarado que se llama YYINITIAL y que es el estado inicial del analizador léxico. Si la especificación no utiliza otros estados, el analizador léxico permanecerá en el estado YYINITIAL durante toda su ejecución. Estados • Si la especificación utiliza otros estados definidos por el programador, hay que declararlos previamente, en la sección de directivas de JLex, para poder utilizarlos en las reglas léxicas. • Las reglas léxicas empiezan con una lista opcional de estados: [<ESTADO1[, ESTADO2[, ESTADO3 ...]]]>] regla_con_estado { acción } • Para esta línea, el comportamiento del analizador léxico será el siguiente: siguiente: ▫ Si el analizador se encuentra en el ESTADO, la regla regla_con_estado estará activada. Esto es, se comparará su correspondencia con los lexemas de entrada. ▫ Si el analizador no se encuentra en ninguno de los estados <ESTADOi>, la regla regla_con_estado no estará activada. Es decir, no se comparará su correspondencia con los lexemas de entrada. ▫ Si una regla no contiene ningún estado en su lista previa de estados, la regla estará siempre activa. No hace falta declarar el estado inicial YYINITIAL. Estados transición a otro estado, se utiliza la función yybegin • Para hacer una (ESTADOi), dentro del bloque de acciones de la expresión regular que identifica la transición. • Actividad 6 Crea el explorador del ejemplo anterior. Comprueba que, para la entrada siguiente, el programa no funciona correctamente: /* Comentario 1 * Comentario 2 */ ¿Qué alternativa soluciona este problema? Tratamiento de errores • Tal y como habréis podido comprobar en la actividad anterior, si el explorador generado recibe una entrada que no encaja con ninguna regla, se genera un error y el explorador aborta su ejecución. • Esto hace que sea necesario que la especificación léxica incluya todas las reglas necesarias para encajar todas las posibles cadenas que pueden encontrarse en la entrada que tiene que analizar el explorador. • A menudo, esta tarea es la más compleja de toda la especificación léxica y, lamentablemente, no existe ninguna norma ni algoritmo para afrontarla. Sin embargo, se presentan unas pocas recomendaciones: ▫ Introducir una regla, como por ejemplo [\t\n\b\r ]+, para encajar los caracteres blancos (espacio, tabulación, retorno de carro, etcétera), si no tienen ninguna función específica dentro del lenguaje. ▫ Introducir, como última regla, .+ para encajar todo lo que no coincida con las reglas propias del lenguaje. Hay que considerar que el carácter de nueva línea no encajará con esta regla y controlar que no enmascare las otras reglas del lenguaje. Tratamiento de errores • Utilizar estados léxicos para activar y desactivar reglas que evalúen parcialmente los lexemas del lenguaje, tal y como se muestra en la actividad anterior. • Hay que ir con cuidado con la negación de las clases de caracteres; su significado no siempre es el que se podría intuir en una lectura precipitada. Tratamiento de errores Tratamiento de errores Tratamiento de errores Tratamiento de errores Sección de directivas • La sección de directivas está comprendida entre los dos delimitadores “%%”. 1) Directivas. Las clasificaremos en los tipos siguientes: 1.a) Directivas que afectan a las reglas léxicas. 1.b) Directivas que afectan al tipo de datos retornado por la función de exploración. 1.c) Directivas que afectan al formato de la entrada o la sortida. 1.d) Directivas que particularizan las propiedades de la clase del explorador. Las directivas JLex se inician con el carácter “%” y van seguidas de un identificador de la directiva. Cada directiva de JLex tiene que ocupar una sola línea y el carácter “%” debe ser el primer carácter de la fila. Sección de directivas 2) Código añadido a la clase. Lo clasificaremos en los tipos siguientes: 2.a) Código que se añade al interior de la clase. 2.b) Código de inicialización de la clase. 2.c) Código para el tratamiento del final del archivo. 2.d) Código para el tratamiento de excepciones. 3) Declaración de macros. Todas las macros utilizadas en la sección de reglas léxicas tienen que declararse en esta sección. Sección de directivas: Declaración de macros La declaración de una macro es una secuencia de la forma: nombre = expresión_regular que que hace que JLex substituya cada aparición de nombre, dentro las reglas léxicas, por la expresión_regular explicitada. Sección de directivas: Declaración de macros Directivas sobre reglas léxicas • Declaración de estados Los estados se declaran mediante la directiva %state: %state ESTADO1 [, ESTADO2[, ESTADO3 ...]]] donde ESTADOi es un nombre de identificador válido (secuencia de letras, dígitos y subrayados, iniciada con una letra o subrayado). El uso de comas entre los nombres de los estados es opcional. Una directiva %state puede contener la declaración de varios estados. La directiva %state puede utilizarse cero, una o varias veces. La declaración de los estados utilizados en las reglas léxicas es obligatoria.} En caso contrario, se producirá un error de compilación en el código generadopor JLex. Directivas sobre reglas léxicas • Sensibilidad a mayúsculas La directiva %ignorecase, hace que el explorador generado por JLex encaje con los caracteres independientemente de si están en mayúsculas o minúsculas en la entrada Directivas sobre reglas léxicas • Activación de valores léxicos Para que los valores léxicos: − int yychar − int yyline estén estén disponibles para las acciones léxicas hace falta activarlos con las directivas siguientes: %char %line Por defecto, estas variables están desactivadas y no son accesibles a menos que se introduzcan las directivas mencionadas. Directivas de modificación del tipo retornado • Tal y como ya se ha comentado, la función yylex() retorna, por defecto, un valor de tipo Yytoken. Es decir, el código de la clase Yylex, generado por Jlex es: El tipo int de Java es el tipo entero primitivo del lenguaje, con un tamaño de 32 bits. • Con esta declaración, las acciones léxicas deber retornar valores enteros. Directivas de modificación del tipo retornado • Directiva %intwrap. Con esta directiva, la función yylex() retorna un valor de tipo java. lang.Integer: class Yylex {... public java.lang.Integer yylex () { ... } Directivas de fin de archivo • El valor de final de archivo (eof) ▫ El explorador generado por JLex necesita un símbolo para representar el valor de final de archivo. Por defecto, este valor es null. ▫ Cuando, utilizando la directiva %type, se modifica el tipo del testigo retornado por el explorador, es necesario asegurarse de que el nuevo tipo acepta el valor null como un valor perteneciente a su tipo de datos o bien cambiar el valor retornado por la función al encontrar el final del archivo. Los tipos primitivos int, double, ... de Java no reconocen el null como un valor de su tipo. Si sólo ponemos la directiva: %type int se producirá un error de compilación del explorador generado, al retornar el valor null como si fuera un valor de tipo int. Directivas de fin de archivo Algunas directivas para definir estos casos son: Directivas de fin de archivo Directivas modificadoras de la clase • Por defecto, JLex genera una clase que implementa el explorador correspondiente a la gramática, tal que: − La clase se llama Yylex. − La clase no tiene ningún modificador de acceso. − La función principal del explorador tiene el nombre yylex(). • Se dispone de las siguientes directivas para modificar estas características: − %class nombreClase: Cambia el nombre de la clase generada, Yylex, por nombreClase. En este caso, será necesario cambiar toda referencia a la clase Yylex por el nuevo nombre de la clase. Por ejemplo: Directivas modificadoras de la clase − %public Hace que la clase generada sea pública. − %function nombreFunción Cambia el nombre de la funcion yylex() por nombreFunción. Como en el caso del cambio de nombre de la clase, hará falta adecuar las llamadas a la función yylex(), usando el nuevo nombre. Por ejemplo: Directivas modificadoras de la clase • La tabla siguiente presenta las modificaciones explicadas y el resultado obtenido: • Además, con la directiva %implements, JLex permite especificar la interfaz que implementará la clase Yylex. • Su sintaxis es: ▫ %implements nombreInterficie ▫ Y el resultado sería: ▫ class Yylex implements nombreInterfaz{ ... Formatos de archivos y texto • La clase Yylex generada por JLex dispone de dos constructores que aceptan un único argumento: el flujo de entrada a explorar. Esta entrada puede ser de los tipos java.io.InputStream o java.io.Reader. Si se pretende generar un explorador que acepte caracteres unicode, es recomendable utilizar el constructorjava.io.Reader • Por defecto, el explorador generado lee y escribe en archivos de texto ASCII. Esta tabla de caracteres contiene los códigos, con tamaño de carácter de siete bits, comprendidos entre 0 (NUL) y 127 (sin representación). Esto hace que, al encontrar el archivo de entrada, caracteres como ñ (ASCII 164), Ç (ASCII 128) o è (ASCII 138) generen una excepción del tipo ArrayIndexOutOfBoundsException. La clase java.io.InputStream lee bytes, de 8 bits, retornando un valor en un rango de 0 a 255. La clase java.io.Reader lee caracteres usando 16 bits, entre los valores 0 y 65.535. Formatos de archivos y texto • Para ampliar este alfabeto e incluir todos los caracteres de hasta 8 bits (Extended ASCII), comprendidos entre 0 (NUL) y 255, debe utilizarse la directiva: %full • Si se desea que el explorador soporte una entrada en alfabeto unicode (códigos entre 0 y 65.535): ▫ Debe usarse la directiva %unicode, ▫ Debe usarse el constructor java.io.Reader, y ▫ El Reader utilizado debe manipular correctamente la traducción de los caracteres en el formato nativo a unicode. Formatos de archivos y texto • Un caso particular de compatibilidad en la conversión de caracteres es el código de nueva línea, que en el caso del sistema operativo UNIX se representa con el carácter “\n” y en sistemas operativos basados en DOS, con la secuencia “\r\n”, consistente en el carácter de retorno de carro y el de nueva línea. En este caso, la directiva: %notunix • hace que tanto el carácter de retorno de carro (\r) como el de nueva línea (\n) se reconozcan como un solo carácter de nueva línea. Este aspecto es importante para asegurar la independencia de la plataforma Java. • Puede parecer que hay limitaciones en cuanto al tamaño de los caracteres debido a todo lo expuesto. No obstante, internamente los caracteres se procesan usando caracteres de Java de 16 bits, aunque no se da soporte a toda la gama de valores. Código añadido a la clase • En la sección de directivas también se puede incluir código de usuario, que hemos clasificado en los tipos siguientes: ▫ ▫ ▫ ▫ Código interno a la clase. Código de inicialización de la clase. Código de final de fichero. Código de tratamiento de excepciones Código añadido a la clase • Código interno La directiva: %{ ...<Código>... %} permite al usuario escribir código Java que será copiado en la clase del explorador: class Yylex { ... <Código>... } Los símbolos %{ y %} deben situarse al principio de la línea. Esto permite la declaración de variables y funciones internas a la clase del analizador léxico generado. Código de inicialización de la clase • Código de inicialización de la clase La directiva: %init{ ...<Código>... %init} Como norma, los nombres de variables que empiecen con yy deberían evitarse, ya que es el formato propio de las variables generadas por JLex dentro del explorador. permite al usuario añadir código al constructor de la clase del explorador: class Yylex { Yylex () { ... <Código>... } } Esta directiva se utiliza para realizar inicializaciones dentro del constructor de la clase. Código añadido a la clase • Código de tratamiento de excepciones Todo el código que hemos insertado en el explorador puede lanzar excepciones, que deberán ser declaradas. • Éstas son: ▫ Para declarar las excepciones que pueden lanzarse en el código de inicialización de la clase (directiva %init{... %init}), se utiliza la directiva: %initthrow{ excepción1 [, excepción2 [,...]] %initthrow} ▫ El código Java especificado, con la lista de excepciones, se copiará en la declaración del constructor del explorador: class Yylex { Yylex () throws excepción1 [, excepción2 [, ...]] { ... } } Código añadido a la clase ▫ Para declarar las excepciones que pueden lanzarse en el código de final de fichero (directiva %eof{... %eof}), se utiliza la directiva: %eofthrow{ excepción1 [, excepción2 [...]] %eofthrow} ▫ Esta directiva modificará la función que se llama al llegar al final de fichero: void private yy_do_eof () throws excepción1 [, excepción2 [...]] { ... } ▫ Hay que recordar que el código de esta función vendrá dado, en parte, por el código definido en la directiva %eof{..%eof}. Código añadido a la clase ▫ Para declarar las excepciones que pueden lanzarse en el código de las acciones léxicas, se utiliza la directiva: %yylexthrow{ excepción1 [, excepción2 [...]] %yylexthrow} ▫ Este código se añadirá a la función yylex(): Yytoken yylex () throws excepción1 [, excepción2 [...]] { ...} • Finalmente, tanto el código de usuario (sección 1) como el código interno a la clase (%{...%}) debe incluir sus propios mecanismos de tratamiento de excepciones. Sección de código de usuario • El código de usuario precede a la primera directiva %%. Este código se copia literalmente al principio del código del explorador generado por JLex. • Es habitual utilizar esta sección para incluir: ▫ Las declaraciones de importación de clases que hagan falta. ▫ Una declaración package. ▫ El código de les clases auxiliares que se precisen. Criterios para acelerar el explorador A la hora de definir la especificación léxica de nuestro lenguaje, podemos tener en cuenta los siguientes criterios para generar un explorador más rápido: • Cuanto más largo es el lexema encajado, más rápido es el explorador. Normalmente, la longitud de los lexemas viene definida por el lenguaje así que parece un criterio con poco sentido. Un ejemplo de aplicación sería la eliminación de comentarios por parte del preprocesador. • En general, la velocidad del explorador no depende de la cantidad de reglas que tenga la especificación. • Evitar la activación de yyline y yychar, salvo que sea necesario. • Evitar las reglas que requieran backtracking. Backtracking ...
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