El analizador léxico lo hemos realizado con la herramienta JFlex, el cual es una herramienta para generar analizadores léxicos y es para lenguaje Java. Hemos decidido utilizado está herramienta porque creemos que nos reduce el esfuerzo de tener que programar un analizador léxico sin ayuda de una herramienta, de esta forma sólo nos tenemos que preocupar de diseñar las expresiones regulares, con sus acciones correspondientes, y JFlex nos genera el “switch” que reconoce a esas expresiones. Para construir el analizador léxico basta con darle las expresiones regulares formadoras de los componentes léxicos que componen el lenguaje y las acciones semánticas asociadas al reconocimiento de cada token y JFlex te construye el analizador léxico para esas expresiones. En caso que una misma cadena de caracteres se pueda corresponder con dos o más patrones o reglas JFlex seleccionará seguirá leyendo caracteres de la entrada hasta que no case con ningún patrón en cuyo caso devolverá el carácter a la entrada, retrocediendo el puntero de lectura del archivo una posición y eliminará el carácter de la cadena actual que está intentando reconocer. Después de hacer esto, si ya sólo se puede corresponder con un patrón se reconoce la palabra como un token correspondiente a ese patrón y si aún existe ambigüedad respecto a que patrón podría pertenecer la palabra se escoge la primera regla que se encuentre respecto a la que haya ambigüedad. Una vez que se haya reconocido un conjunto de caracteres como pertenecientes a un token se ejecutan el bloque de sentencias asociadas al reconocimiento de ese patrón. Como casi siempre que se utiliza un analizador léxico se hace conjuntamente a un analizador sintáctico y semántico, supongo que los token los solicita el analizador sintáctico al léxico mediante la función “siguiente carácter”. Esta función produce que el léxico empiece a procesar caracteres del fichero de entrada hasta que encuentre el conjunto de caracteres mayor que pueda reconocer caso en el que devuelve token al analizador sintáctico y se queda a las espera de la siguiente orden “siguiente token”, es decir, devuelve el control al analizador sintáctico. EXPRESIONES REGULARES RECONOCIDAS Los tipos de token que reconocemos en el analizador léxico son: número entero, número real, operador de asignación, operador lógico, operador relacional, operador matemático, palabra clave, puntero, identificador, cadena (tipo string), carácter (tipo char), signo de puntuación y fin de fichero. Cada tipo de token tiene asociado una, o varias, expresiones regulares para poder identificarlos. La notación que he seguido para describir los patrones que reconocen a los token es la usada por JFlex por lo que antes de describir los patrones de las expresiones regulares daré una pequeña explicación de la notación de JFlex. “”: sirve para encerrar cualquier cadena de literales. Por regla general no es necesario encerrar los literales entre comillas a no ser que incluyan símbolos especiales, esto es, el patrón “WHILE” y el patrón WHILE son equivalentes; pero para representar p.ej. el inicio de comentario en Modula-2 sí es necesario entrecomillar los caracteres que componen al patrón: “(*”, ya que éste contiene, a su vez, símbolos especiales. \: hace literal al siguiente carácter. Ej.: \” reconoce unas comillas. También se utiliza para expresar aquellos caracteres que no tienen representación directa por pantalla: \n para el retorno de carro, \t para el tabulador, etc. [ ]: permiten especificar listas de caracteres, o sea uno de los caracteres que encierra, ej.: [abc] reconoce o la ‘a’, o la ‘b’, o la ‘c’, ( [abc] / (a|b|c) ). Dentro de los corchetes los siguientes caracteres también tienen un sentido especial: o -: indica rango. Ej.: [A-Z0-9] reconoce cualquier carácter de la ‘A’ a la ‘Z’ o del ‘0' a ‘9'. o ^: indica compleción cuando aparece al comienzo, justo detrás de “[”. Ej.: [^abc] reconoce cualquier carácter excepto la ‘a’, la ‘b’ o la ‘c’. Ej.: [^A-Z] reconoce cualquier carácter excepto los de la ‘A’ a la ‘Z’. ?: aquello que le precede es opcional1. Ej.: a? / ( a | g ). Ej.: [A-Z]? reconoce cualquier letra de la ‘A’ a la ‘Z’ o bien g. Ej.: a?b / ab | gb. .: representa a cualquier carácter (pero sólo a uno) excepto el retorno de carro (\n). Es muy interesante porque nos permite recoger cualquier otro carácter que no sea reconocido por los patrones anteriores. |: indica opcionalidad (OR). Ej.: a|b reconoce a la ‘a’ o a la ‘b’. Ej.: .|\n reconoce cualquier carácter. Resulta curioso el patrón (.|\n)* ya que por aquí entra el programa entero, y como yylex() tiene la premisa de reconocer el lexema más largo, pues probablemente ignorará cualquier otro patrón. También resulta probable que durante el reconocimiento se produzca un error por desbordamiento del espacio de almacenamiento de la variable yytext que, recordemos, almacena el lexema actual. *: indica repetición 0 o más veces de lo que le precede. +: indica repetición 1 o más veces de lo que le precede. ( ): permiten la agrupación (igual que en las expresiones aritméticas). { }: indican rango de repetición. Ej.: a{1,5} / aa?a?a?a? Las llaves vienen a ser algo parecido a un * restringido. También nos permite asignarle un nombre a una expresión regular para reutilizarla en múltiples patrones; esto se verá más adelante. !: encaja con cualquier lexema excepto con los que entran por el patrón que le sucede. Ej.: !(ab) encaja con cualquier cosa excepto el lexema “ab”. Una vez que ya se conoce la notación de JFlex comenzamos a redactar los patrones: LETRAS= [a-zA-Z] DIGITOS= [0-9] NUMERO_Entero= {DIGITOS}+ NUMERO_Real= {DIGITOS}+("."{DIGITOS}+)?(e("+"|"-")?{DIGITOS}+)? OperadorLogico= “and” | “or” | “not” OperadorMatematico= “+” | “-” | “*” | “/” | “div” | “mod” | “shr” | “shl “| “in” PalabraClave= “And” | “Array” | “Begin” | “Case” | “Char” | “Const” | “Div” | “Do” | “Downto” | “Else” | “End” | “File” | “For” | “Function” | “Goto” | “If” | “In” | “Integer” | “Label” |”Mod” |”Nil” |”Not” |”Of” |”Or” |”Packed” |”Procedure” | “Program” |”Record” |”Repeat” |”Set” |”Shl” |”Shr” |”String” |”Then” |”To” |”Type” |”Unit” |”Until” |”Uses” |”Var” |”While” |”With” Puntero= “^” IDENTIFICADOR={LETRAS}({LETRAS}|{DIGITOS})* CHAR= (\'[^\']\')|(\'\'\'\')|("#"{NUMERO_Entero}) CADENA= (\"[^\"]*\")|(\"[^\"]*\"(\"[^\"]*\")*) SALTOLINEA= \r|\n|\r\n OperadorRelacional= “<“ | “<=“ | “>“ | “>=“ | “=“ | “<>“ SignoPuntuacion=,|"."|;|:|\"|\'|\(|\)|\[|\] OperadorAsignacion= := ESPACIOS=" "+ ERRORCARACTER=[^LETRAS] LITERALMALO="#"{LETRAS}+ NUMEROMALO={DIGITOS}+"."{DIGITOS}+"."{DIGITOS}+ DESCRIPCIÓN DE LOS TOKENS Los token los he diseñado como objetos con 4 atributos en vez de los 2 habituales. A los dos atributos tradicionales “tipo de token” y “atributo” le he añadido el número de línea y de columna del token para facilitar la gestión de errores, ya que de esta forma es mucho más fácil determinar la localización del error ya sea léxico, sintáctico o semántico, bastaría con mirar la línea y columna del primer token que produce el error y notificar el error en dicha línea y columna. Para poder consultar cualquiera de los cuatro atributos se invoca a su correspondiente función getter. El campo “tipo de token” es una clase que imita a un enumerado tradicional como los de C o Pascal. Los posibles valores de este enumerado y sus correspondientes atributos son: IDENTIFICADOR: representa a los identificadores del programa, pero que no sean palabras clave del programa, es decir, definidos por el programador. Su atributo es un puntero a su posición en la tabla se símbolos. PALRESER: representa a todas las palabras clave o reservadas del programa. Su atributo es un enumerado con la palabra clave de la cual se trata. OPERAMATE: representa a los operadores matemáticos. Su atributo es un enumerado con el operador del cual se trata. OPERARELA: representa a los operadores relacionales. Su atributo es un enumerado con el operador relacional del cual se trata. OPERAASIG: representa al operador de asignación. Su atributo es un puntero a null ya que no se requiere de información extra. OPERALOGI: representa a los operadores lógicos. Su atributo es un enumerado con el operador lógico del cual se trata. SIGNOPUNTUACION: representa a los signos de puntuación. Su atributo es un enumerado con el signo de puntuación del cual se trata. CADENA: representa a las cadenas de caracteres. Las cadenas de caracteres se considerar a todo lo que se encuentre entre dos comillas dobles. Su atributo es un String de Java con el conjunto de caracteres de la cadena. LITERAL: representa a los caracteres individuales. Los caracteres individuales van encerrados entre comillas simples y sólo puede ir uno, o también se puedes representar con una almohadilla y su número ASCII. Su atributo es un String de Java con carácter. ENTERO: representa a los números enteros. Su atributo es un Integer de Java con el valor del número entero. REAL: representa a los números reales. Su atributo es un double de Java con el valor del número real. PUNTERO: representa al signo de puntero. Su atributo es un puntero a null ya que no se requiere de información extra. FINFICHERO: representa al fin del fichero. Su atributo es un puntero a null ya que no se requiere de información extra. GESTIÓN DE ERRORES Para gestionar los posibles errores que se produzcan al procesar la información de entrada, ya sea en el analizador léxico, sintáctico o semántico, se tiene un módulo que interaccionará con todos ellos, el gestor de errores. Al gestor de errores se le comunican los mensajes de error conjuntamente con su línea y columna. La forma de comunicar un error es mediante la función añadir error y la forma de mostrarlos mediante la función mostrar errores que los muestra por pantalla (sirve para depurar) y también genera un documento con los errores acaecidos, en caso de que se haya producido alguno. Los errores que hemos pensado son errores al introducir caracteres no reconocidos fuera de las cadenas de caracteres (ERRORCARACTER), la formación de un carácter de forma incorrecta colocando letras en vez de un número detrás de una almohadilla (LITERALMALO), formar un número decimal con dos puntos (NUMEROMALO) y cambiar el orden de los caracteres del operador de asignación poniendo primero el igual y después los dos puntos (ASIGNACIONMALA). TABLA DE SÍMBOLOS Nuestra tabla de símbolos sólo tendrá una tabla para identificadores, en realidad varias para gestionar los ámbitos pero todas de identificadores, ya que no JFlex nos reconoce directamente los distintos tipos de token y no es necesario que los introduzcamos en la tabla de símbolos ni las palabras clave, ni operadores, ni cadenas, ni los caracteres. Como para Java todo son punteros no hay problema en que en el token vaya la cadena de texto directamente ya que en realidad es solamente un puntero a esta. Tampoco necesitamos una tabla con palabras reservadas porque JFlex nos las identifica y no hay que comprobar que este en dicha tabla, insertándola si no está o si está indicando que palabra clave es. CLASES En e paquete principal tenemos el analizador léxico (Liner), el gestor de errores y la clase token. La clase del analizador léxico la hemos llamado Liner, los parámetros son la ruta del archivo que se vamos a analizar, el gestor de errores que se va a utilizar y la tabla de símbolos. Se incorporan estos parámetros porque sino no podría comunicar entre ellos de esta forma se utiliza el puntero que se tiene para hacer llamadas a añadir errores y a insertar de la tabla de símbolos. Para que el analizador léxico te devuelva el siguiete token se utiliza la función nextToken. El Gestor de errores es un array que va guardando los mensajes con su correspondiente línea y columna. En el paquete tipos tenemos todos los tipos que necesitamos, básicamente los enumerados.