ANTLR es una herramienta que integra la generacin de analizadores lxicos, sintcticos, rboles de sintaxis abstracta y evaluadores de atributos. ANTLR est escrita en Java y genera Java, C++ y C#. Todos los detalles de esta herramienta se encuentran en su pgina oficial http://www.antlr.org/. En esta prctica trabajaremos con un lenguaje muy sencillo, que nos servir para presentar la notacin que usa ANTLR para la especificacin de analizadores lxicos y sintcticos. Aunque haremos algunas modificaciones sobre la solucin propuesta en el enunciado, no ser hasta las prcticas 2 y 3 cundo estudiemos en profundidad los problemas asociados a los analizadores lxicos y sintcticos. Todas las herramientas necesarias para realizar la prctica estn instaladas en las aulas de laboratorio.
Un lenguaje ejemplo
El siguiente fragmento muestra el fichero de configuracin de un determinado sistema. El lenguaje es extremadamente simple y slo permite asociar valores numricos a una serie de variables:
ANTLR permite especificar cada analizador (lxico, sintctico, semntico) en un fuente independiente o en un nico fuente. En este primer ejemplo optaremos por utilizar un solo fuente. A medida que los analizadores sean ms complejos ser recomendable especificar cada uno por separado. El analizador para nuestro lenguaje tendra un aspecto como este en ANTLR:
class Anasint extends Parser; entrada : (asignacion)* ; asignacion : IDENT "=" NUMERO ";" ;
La estructura de un fichero fuente ANTLR
Los ficheros de gramticas tienen la siguiente estructura:
header{ /* cdigo de cabecera */ } options{ /* opciones comunes a toda la especificacin */ } ////////////////////////////// // Definicin de analizadores ////////////////////////////// ...
Las dos primeras secciones son opcionales. La seccin header sirve para incluir cdigo de cabecera, fundamentalmente instrucciones import o definicin del paquete al que pertenecer la clase del analizador. La seccin options permite configurar algunos aspectos de ANTLR a travs de opciones, que se representan mediante asignaciones del tipo nombre_opcion = valor;. Tras estas secciones se incluye la definicin de los analizadores, ANTLR permite especificar todos los analizadores en un nico fuente o dedicar un fichero independiente para cada uno. La definicin de un analizador sigue la siguiente estructura:
class nombre_analizador extends tipo_analizador; options { /* opciones especficas del analizador */ } tokens { /* definicin de tokens */ } { /* cdigo propio del analizador */ }
//--------------------------- // Zona de reglas //--------------------------- ...
donde:
La primera instruccin sirve para establecer cual ser el nombre del analizador y de qu clase heredar (tipo_analizador). En el caso de analizadores lxicos se extender la clase Lexer, para los sintcticos se usar Parser y TreeParser para los recorridos de rboles de sintaxis abstracta. La seccin options ser opcional y servir para especificar opciones propias del analizador. Se puede, por ejemplo, definir el nmero de smbolos de anticipacin en el reconocimiento LL(k), importar tokens, etc. La seccin tokens tambin es opcional, permite definir nuevos tokens que se aaden a los definidos en otros analizadores. La zona de cdigo nativo sirve para incluir declaraciones de atributos y mtodos que se aaden a las que de forma automtica se generan para la clase que implementa el analizador. Por ltimo la zona de reglas constituye el ncleo de la especificacin. En los ejemplos previos ya hemos mostrado el aspecto que tienen estas reglas. Los comentarios se pueden incluir en cualquier parte del fuente, permitindose comentarios de una sola lnea (con el smbolo //) y de varias lneas (con los smbolos /* y */ para marcar el comienzo y final respectivamente).
Ejecutando el intrprete
Hasta ahora hemos especificado los distintos analizadores, pero an nos queda por escribir un ltimo fuente para poder ejecutarlos, el que describe la clase que contiene el mtodo main:
FileInputStream fis = new FileInputStream("entrada.txt"); Analex analex = null; Anasint anasint = null; analex = new Analex(fis); anasint = new Anasint(analex); anasint.entrada();
}catch(ANTLRException ae) {
System.err.println(ae.getMessage());
}catch(FileNotFoundException fnfe) {
System.err.println("No se encontr el fichero");
} } }
El cdigo es bastante claro y en general se limita a: Abrir el fichero "entrada.txt". Crear un objeto de cada analizador. Lanzar el mtodo anasint.entrada()para realizar el anlisis sintctico. Capturar las distintas excepciones que se hayan podido lanzar durante el proceso.
Lectura de flujos de bytes y flujos de caracteres
Los reconocedores generados por ANTLR pueden adaptarse con facilidad para leer desde distintos flujos de entrada. La clase que implementa el anlisis lxico (en nuestro ejemplo Analex) es la encargada de recibir ese flujo de entrada y su constructor puede recibir tanto objetos de la clase InputStream para recibir flujos de bytes, como de la clase Reader para recibir flujos de caracteres.
Como ya hemos visto en el ejemplo anterior, FileInputStream (una subclase de InputStream) nos sirve para procesar entradas almacenadas en un fichero. En el siguiente ejemplo veremos cmo StringReader (una subclase de Reader) nos permitir aplicar el anlisis lxico al contenido de una cadena de caracteres que a su vez ha sido leda desde el teclado:
InputStreamReader isr = new InputStreamReader(System.in); BufferedReader br = new BufferedReader(isr); String linea = br.readLine(); while (!linea.equals("$")) { Analex analex = null; Anasint anasint = null; analex = new Analex(new StringReader(linea)); anasint = new Anasint(analex); anasint.asignacion(); linea = br.readLine();
} }catch(ANTLRException ae) {
System.err.println(ae.getMessage());
}catch(IOException ioe) {
System.err.println(ioe.getMessage()); } } }
Los aspectos ms interesantes de este programa son:
Se construye el flujo de caracteres isr aplicando InputStreamReader sobre el flujo de bytes System.in. A partir del flujo isr se obtiene br, un flujo de caracteres con buffer (BufferedReader) que puede leerse lnea a lnea. La entrada del teclado se lee lnea a lnea en la variable linea en un bucle que se repite hasta que se introduce $, el carcter elegido para indicar el fin del proceso. Para analizar el contenido de la variable lnea basta con crear a partir de ella un flujo de entrada con el constructor StringReader. Dicho flujo se utilizar en la construccin del analizador lxico.
EJERCICIOS
1. Compilar los fuentes presentados en el enunciado y comprobar el funcionamiento del reconocedor de expresiones propuesto.
2. Modificar la clase principal de manera que en lugar de leer siempre del fichero "entrada.txt" reciba el nombre del fichero a procesar a travs del parmetro args[0] del mtodo main.
3. Adaptar el reconocedor de manera que la entrada se lea del teclado y no de un fichero. La entrada se procesar lnea a lnea y por tanto no ser necesario tener en cuenta el smbolo ";" como separador. El proceso terminar cuando se introduzca la entrada $. Ante entradas correctas el intrprete no emitir ningn mensaje mientras que para entradas incorrectas se mostrar el mensaje de error generado por ANTLR. Se detecta bien una entrada incorrecta de la forma max_robots = 12 12? Cmo se puede solucionar este problema?
4. Ampliar el lenguaje del apartado 2 con los siguientes elementos:
Valores tipo booleano (true, false), cadenas y rutas de ficheros (siempre empiezan con "/"): disparo_efectuado = TRUE; directorio_ficheros_cfg_robots = /home/robot/work; mensaje = "el robot ha hecho blanco";
Variables de usuario definidas con la palabra instruccin SET, los nombres empezarn con "$":
SET $nombre_robot = Legolas;
5. Ampla el lenguaje anterior de manera con una instruccin condicional if, que permita decidir si se ejecuta una parte del fichero en funcin del valor de una variable booleana (predefinida o de usuario):
SET $multi_robot = TRUE; IF ($multi_robot) { max_robots = 10; }