Está en la página 1de 7

http://www.albertnogues.

com 1 Compilando un compilador de Java

Compilando un compilador de Java


Introduccin.
Haciendo una prctica de la universidad en PCCTS, nos tocaba implementar un compilador con todas sus fases. Anlisis sintctico, lxico, semntico, y la generacin de cdigo. (La parte de optimizacin de cdigo solo se daba a nivel terico). Usbamos rboles AST (Abstract Syntax Tree) para guardar los datos y las estructuras intermedias que bamos generando. Aqu os presentar una versin muy simplificada para los que alguna vez os habis preguntado como implementar un compilador. De echo la implementacin que har aqu nams reconocer expresiones muy bsicas como sumas y restas. Y tambin asignaciones. As nos evitaremos muchos problemas que tendramos si aceptaramos funciones, procedimientos, declaracion de tipos estructurados, paso de parmetros por valor o referencia La herramienta que usaremos se llama ANTLR, que es la versin actualizada de PCCTS i esta, es basada en java en lugar de C++. El tutorial est basado en la versin 3.0 de antlr. Es una versin nueva con cambios significativos respecto a la versin 2.X, pero mejor empezar con una versin que tenga ms recorrido. A parte de antlr necesitaremos un programa para disear la gramtica. Optaremos por ANTLRWorks. Lo podemos descargar aqu: http://antlr.org/works/index.html Con este .jar ya tenemos tambin antlr ya que viene incluido dentro del paquete. Hay que tener presente que necesitamos tambin tener la mquina de java instalada en nuestro pc. Una vez descargado, damos doble click y debera arrancar el programa. Sino desde la consola con: java jar Nombredelfichero.jar Ahora ya estamos en disposicin de empezar a disear la gramtica.

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;

tokens { PLUS = '+' ; MINUS = '-' ; }

@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;} )* ;

factor returns [int value] : NUMBER {$value = Integer.parseInt($NUMBER.text);};

/*-----------------------------------------------------------------* LEXER RULES *------------------------------------------------------------------*/ ID : ('a'..'z'|'A'..'Z')+ ;

http://www.albertnogues.com 3 Compilando un compilador de Java NUMBER : (DIGIT)+ ;

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.

Generando cdigo y compilando.


El siguiente paso es generar el cdigo del parser y del lexer, es decir, de la parte del compilador que se encargar de leer la entrada y mirar que sea correcta a nivel sintctico, y el lexer que es el encargado de realizar las operaciones y comprobar que todo funcione correctamente. Para generar el cdigo de mbos, en el ANTLRWorks, vamos a Generate Esto nos crear dos ficheros java que hemos de compilar. Generate Code.

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.

http://www.albertnogues.com 7 Compilando un compilador de Java

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

También podría gustarte