Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Pasos previos.
Bien, antes de empezar a disear nuestra gramtica, os explicar un poco la estructura de un fichero .g o gramtica de antlr. Para arrancar nuestro compilador, dado que usamos java y todo se traducir a este lenguaje podemos optar por meter todo el cdigo del compilador dentro del fichero .g en el sitio que pertoque o escribirlo en ficheros aparte. Si lo hacemos en ficheros aparte deberemos compilar desde la lnea de comandos todos los .java que hayamos aadido. Como lo que quiero es hacerlo entendible, escribiremos todo el cdigo en un solo fichero. Con esto conseguiremos que desde ANTLRWorks podamos generar todos los ficheros .java y arrancar una lnea de comandos con nuestro compilador.
http://www.albertnogues.com 2 Compilando un compilador de Java La estructura del fichero de gramtica es la siguiente: grammar SimpleCalc;
@members { public static void main(String[] args) throws Exception { SimpleCalcLexer lex = new SimpleCalcLexer(new ANTLRFileStream(args[0])); CommonTokenStream tokens = new CommonTokenStream(lex); SimpleCalcParser parser = new SimpleCalcParser(tokens); try { parser.expr(); } catch (RecognitionException e) { e.printStackTrace(); } } }
/*-----------------------------------------------------------------* PARSER RULES *------------------------------------------------------------------*/ expr : ID '=' op {System.out.println($ID.text +"="+ $op.value);}; op returns [int value] : e=factor {$value = $e.value;} ( PLUS e=factor {$value += $e.value;} | MINUS e=factor {$value -= $e.value;} )* ;
WHITESPACE : ( '\t' | ' ' | '\r' | '\n'| '\u000C' )+ { $channel = HIDDEN; } ; fragment DIGIT : '0'..'9' ;
Bueno como seguramente pensaris, esta es la gramtica que he usar para el ejemplo. De hecho no la he diseado yo. Es muy parecida a una que hay en los ejemplos de la pgina de antlr aqu: http://www.antlr.org/wiki/display/ANTLR3/Five+minute+introduction+to+ANTLR+3 o bien aqu: http://www.antlr.org/wiki/display/ANTLR3/Expression+evaluator
He sombreado los bloques para que se vean las partes diferenciadas del fichero. La primera parte es la cabecera del fichero. Aqu hemos de prestar atencin de darle el mismo nombre que tiene el fichero. Os recuerda a java esto, no? Pues bsicamente la idea es la misma. La clase debe llamarse igual que el nombre del fichero, sino, no compilar. El segundo bloque es la declaracin de tokens. Aqu debemos aadir todos los elementos que queremos que reconozca nuestra gramtica. En nuestro caso el signo + y el signo El tercer bloque @ members contiene el cdigo java de nuestra aplicacin. En nuestro caso hemos colocado un main. De hecho el programa funcionar igual sin este cdigo el problema es que no podremos llegar a ver nada si el texto de entrada es aceptado. Lo que hace este cdigo es leer un fichero de texto especificado en la lnea de comandos cuando arrancamos nuestro compilador, eso es args[0]. Dentro del bloque try, llamamos a la primera regla del programa (parser.expr()), esto desencadenar el inicio del programa. El cuarto bloque es las reglas del parser, es decir puramente la gramtica. Aqu podemos ver como una expresin es un ID (identificador, o nombre de variable) el token igual = (si, ya s que lo podramos haber definido en el apartado de tokens, pero para que vierais que tambin podemos ponerlo directamente aqu) y una operacin (la definimos ms abajo). Lo que vemos entre { } es el cdigo java que he escrito yo. Basicamente nos imprimir por pantalla las expresiones que vaya reconociendo. La siguiente regla es la operacin. Tenemos un factor OPERANDO factor. El factor en nuestro caso ser un entero, lo podemos ver en la tercera regla. Lo que hace es almacenar en una variable valor temporal, el resultado de sumar o restar los dos factores. La tercera regla, almacena en una variable llamada valueel resultado de parsear la lectura del fichero a un Entero. El cuarto bloque es la declaracin de los tipos en nuestro caso un ID (identificador de variable) es cualquier nombre de la a-Z que contenga al menos una letra. Esto es debido a la clusala positiva de Klenee (+). El mismo razonamiento para los nmeros, para los espacios en blanco y los dgitos.
http://www.albertnogues.com 4 Compilando un compilador de Java Me he dejando por comentar un par de cosas adrede, porque quera que quedaran claras. Los campos disponen de varios valores (como si fueran una struct) estos son .value y .text. Se entiende que value es un entero y text un campo para almacenar strings, como los ID.
El siguiente paso como he dicho es compilar el programa. Para ello abrimos una consola y escribimos: javac SimpleCalcLexer.java SimpleCalcParser.java Si todo ello funciona correctamente, como debera, nos generar dos ficheros class con el mismo nombre que los java. Podemos arrancar nuestro compilador con: java SimpleCalcparser test.txt El fichero test.txt lo he creado yo con un editor de texto. Simplemente se trata del fichero de nuestro programa. En mi caso lo que he escrito es: A=3+6 Y el resultado de la ejecucin del programa es: A=9 Por tanto, como podemos comprobar ha realizado correctamente la operacin.
Probando el compilador.
Vamos a probar de toquetear un poco nuestro programa. Primero de todo intentamos ver que pasa si cambiamos en nuestro fichero de cdigo nuestra asignacin por esta otra: A=3+v Rpidamente nuestro compilador se quejar y nos dar un error como este:
Como podemos ver se queja de que no reconoce el token v en esa expresin. Es decir un error de missmatch o sea que no coincide lo que espera con lo que le llega. Obvio ya que hemos definido las sumas como sumas de enteros y v no es ningn entero, ya que es un ID. Otras cosas curiosas que podemos observar, aparte de lo limitado que es para el reconocimiento de expresiones, es que el programa no tiene memoria. Si os fijis en el cdigo
http://www.albertnogues.com 5 Compilando un compilador de Java lo veris. No os preguntis donde se almacena el valor de nuestros clculos? Si miris otra vez el cdigo veris que el proceso que hacemos cada vez que se encuentra una expresin bien formada (entindase por una expresin correctamente aceptada) es simplemente imprimirla por pantalla. No guardamos ningn registro de ella. Esto tiene fcil solucin. Veremos cmo arreglar estos problemillas y completar un poco nuestro rudimentario compilador.
Mirando ms all.
Como hemos comentado antes, queremos mejorar nuestro compilador para que, adems de guardar un registro de nuestras asignaciones, nos permita incorporar sumas de variables (ID) declaradas en nuestro programa. El primer paso ser guardar el valor de una asignacin. Es necesario para el siguiente paso, ya que si queremos usarla ms tarde, primero la hemos de tener grabada. Lgico no? Antes de nada, vamos a modificar la gramtica para que nos admita mas de una asignacin. Si os fijis la regla expr, nuestra primera regla silo reconoce una expresin. Para ello cambiaremos el nombre de expr por asig y antes de esta regla aadiremos una nueva regla llamada expr. El cdigo ser algo as:
Con esto ya habremos conseguido reconocer un conjunto de 1* asignaciones. Para guardar las variables usaremos una tabla Hash. Java ya nos proporciona una estructura de datos de este tipo. Se llama HashMap. En la directiva @members debemos aadir nuestra estructura. Quedara algo as:
HashMap variables = new HashMap();
Hemos de importar ahora la clase HashMap. Para esto antlr dispone de un nuevo espacio para indicar los imports de clases java. Aadiremos el siguiente cdigo justo antes de la directiva @members
@header { import java.util.HashMap; }
Con este cambio, solo nos faltar modificar las reglas de la gramtica. En concreto, la regla asig, aparte de que nos muestre la asignacin aceptada, haremos que nos guarde el resultado en la tabla de variables. Dentro del cdigo java entre llaves, aadimos el siguiente:
asig : ID '=' op {System.out.println($ID.text +"="+ $op.value); variables.put($ID.text, new Integer($op.value));};
http://www.albertnogues.com 6 Compilando un compilador de Java Como veis hemos aadido una instruccin para que nos aada en la tabla la variable, con su valor entero. Si probis de ejecutar el cdigo no veris ningn cambio, salvo que en el main imprimis la tabla hash. El siguiente paso es modificar la regla factor para que nos permita operar tambin con variables (ID).
factor returns [int value] : NUMBER {$value = Integer.parseInt($NUMBER.text);} | ID { Integer v = (Integer)variables.get($ID.text); if ( v!=null ) $value = v.intValue(); else System.err.println("Variable no definida: "+$ID.text); };
Si observis hemos aadido una regla nueva con una disyuncin (|). Esta regla busca la variable ID, en la tabla de variables, la parsea a un entero. Si no la encuentra imprime un mensaje de error Variable no definida. Para probarlo usar este juego de pruebas simple: v=7+2 a=3+v El resultado debera ser V=9 y a=12. Veamos si es correcto:
Pues funciona perfectamente. Vamos a probar ahora de aadir una variable que no exista. Cambiamos el juego de pruebas anterior por este: v=7+2 a=3+vr Y vemos como el resultado no es satisfactorio:
Si os fijis la ultima suma s que se realiza, pero la variable inexistente se toma como valor = 0. Esto lo podramos cambiar con un simple ajuste en el cdigo que trata la variable no encontrada.
Ampliaciones.
Bien, hasta aqu ya tenemos algo ms presentable. Ahora podramos mejorarlo aadiendo la generacin de un rbol AST y recorrindolo, con lo que podramos hacer lo mismo que hemos hecho pero ms aproximado a lo que hace un compilador real. Se puede seguir ampliando con ms operaciones. Podis probar de aadir otras operaciones de enteros, como multiplicacin, divisin. El siguiente paso sera aadir algn nuevo tipo. Podramos aadir tipos de coma flotante, pero ya se complicara mucho ms, ya que deberamos mostrar errores cuando intentramos dejar el resultado real en un entero, es decir no admitir la prdida de precisin. Otra ampliacin interesante podra ser aadir el tipo booleano. Esto nos permitira aadir las operaciones boleanas AND, OR, NOT, y tambin aadir algunas mas expresiones enteras que tienen resultados booleanos, como la IGUALDAD, MAYOR, MENOR, MENOR IGUAL, MAYOR IGUAL, DIFERENTE De hecho no sera necesario aadir el tipo booleano para jugar con estas expresiones. Podramos usar el 0 entero como el falso y el 1 por ejemplo como cierto. Los ltimos pasos ya seran la declaracin de tipos estructurados, aadir soporte para declaracin y uso de funciones y sus correspondientes paso por valor, referencia