Tutorial ANTLR y Python II. Creando una gramatica sencilla

Posted on 30 mar 2009

Viene de http://objektblog.wordpress.com/2009/03/29/tutorial-antlr-y-python-i-introduccion-e-instalacion/

Tutorial ANTLR y Python:

Parte I: Introducción e Instalación.

Parte II: Creacion de una gramática sencilla.

Parte III: Creacion e implementación de un AST.

Antes de empezar, el codigo que se usara en los ejemplos esta publicado en el sig. URL: http://rapidshare.com/files/215758595/ejemplos.tar.bz2 Ahh las gramaticas, tan odiadas por muchos. Y no es para asombrarse, ya que tantos problemas que dan, más aún si la herramienta no es nada amigable (como la tan odiada combinacion JLex/CUP), ANTLR es lo suficientemente amigable como para que desarrollemos gramáticas de la manera menos dolorosa posible, teniendo como valor agregado el ANTLRWorks, un IDE que de verdad permite aumentar nuestra eficiencia a la hora de hacer gramáticas para ANTLR. Esta parte se enfocará en generar una gramática simple con la cual se aprenderá de manera básica la estructura y sintaxis de ANTLR.

Pero… Y donde esta el analizador Léxico???

Como dije antes ANTLR simplifica las cosas, con ello, unifica tanto el analizador léxico como el sintáctico, por lo que para ambas tareas usaremos la misma herramienta, evitando los dolores de cabeza al intentar integrar ambos mundos.

Empezando…

Abrimos el ANTLRWorks que instalamos previamente en nuestro DIRECTORIO DEL IDE. y desde consola ejecutamos $ cd $ java -jar antlrworks-1.2.3.jar & Abriremos de esta manera el entorno de trabajo de ANTLR, desde donde podemos hacer clic en el menu File -> New… para crear una nueva gramatica ANTLRWorks. Luego de esto, ya podemos empezar a escribir… Empezaremos definiendo lo primero, el nombre que queremos para nuestra gramatica, para ello escribimos els siguiente texto en el editor:grammar ejemplo; Luego de esto se puede definir diversas opciones para el ANTLR en la directiva Options que ofrece, en este caso especificamos que se generara codigo para python.options{ language=Python; } Y luego viene la gramática…

Especificacion de la gramatica:

Ya especificado el lenguaje, podemos empezar a hacer ya nuestra gramatica. Nuestro objetivo es crear un lenguaje que pueda leer expresiones aritméticas simples de suma, resta y multiplicación. Debo aclarar que el método de análisis de ANTLR es LL(k) descendente, por lo que debemos previamente hacer las transformaciones necesarias a nuestra gramática (quitar recursividad por la izquierda, factorizar, etc).A continuación la gramática que se utilizará …
`grammar ejemplo;
options{
language=Python;
}
program : sentencias
;
sentencias
(expresion NEWLINE )+ | NEWLINE ; expresion
suma ; suma
mult (OPSUM mult)* ; mult
nume (OPMUL nume)* ; nume : ENTERO | ‘(’ expresion ‘)’ ; ENTERO : (‘0’..‘9’)+ ; // Lee enteros postivos NEWLINE: ‘\r’? ‘\n’ ; // lee cambios de linea OPSUM : ‘+’ | ‘-’; // operadores aditivos OPMUL : ‘/’ | ‘’; // operadores multiplicativosDebemos tomar en cuenta que **LOS SIMBOLOS TERMINALES VAN EN MAYUSCULAS**, asi tambien la gramatica tiene que estar factorizada y sin recursividad por la izquierda. la recursividad se puede quitar desde el mismo ANTLRWorks que tiene la opcion "Remove All Left Recursion" en el menú "Refactor"(forma sencilla), o también, escribiéndolos como Gramáticas Regulares es decir es decir transformar la forma recursiva por la derecha (forma avanzada)...e : ep ;ep : OP ep | null ; equivalente a a la expresion regular en la gramatica.e : ep (OP ep) ;` …como se hizo en el ejemplo, pues ANTLR permite la escritura de expresiones regulares desde la misma gramatica, además de hacerla más legible, se vuelve más intuitiva la forma de gramatica regular. Pero bueno, en si lo único que hace esta gramática es revisar si la entrada va bien escrita… todavia faltan las acciones.

Agregando acciones semánticas en la gramatica:

Gracias a que ANTLR usa método descendente, se puede utilizar Atributos Heredados. Trabajaremos en base a atributos heredados, pues es como normalmente se trabaja en ANTLR. Tambien hay que tomar en cuenta que debido a que se estará generando código para Python, obviamente las acciones deberán ir en ese lenguaje.

Agregando acciones, la gramática nos quedaría de la siguiente manera:

`grammar ejemplo;
options{
language=Python;
}
program : sentencias
;//fin program
sentencias
(expresion NEWLINE {print $expresion.valor} )+ | NEWLINE ;//fin sentencias expresion returns
mult { $expresion.valor = $mult.valor }// suma ;//fin expresion suma returns
e1=mult {$valor = $e1.valor } (OPSUM e2=mult { if ($OPSUM.getText() == ‘+’) : $valor = $valor + $e2.valor if ($OPSUM.getText() == ‘-’) : $valor = $valor - $e2.valor } //cerramos atributo )\*;//fin suma mult returns // nume ( OPMUL nume)*
e1=nume {$valor = $e1.valor } ( OPMUL e2=nume { if ($OPMUL.getText() == ‘’) : $valor = $valor * $e2.valor if ($OPMUL.getText() == ‘/’) : if ($e2.valor 0) : $valor = $valor / $e2.valor else: print “Division entre 0\n” } //cerramos atributo ) //cerramos expresion ;//fin mult nume returns : ENTERO { $valor = int($ENTERO.getText()) } | ‘(’ expresion ‘)’ { $valor = $expresion.valor } ;//fin nume ENTERO : (‘0’..‘9’)+ ; // Lee enteros postivos NEWLINE: ‘\r’? ‘\n’ ; // lee cambios de linea OPSUM : ‘+’ | ‘-’; // operadores aditivos OPMUL : ‘/’ | ‘*’; // operadores multiplicativos`

Revisando la gramática tenemos cosas nuevas:

* suma returns nos indica que el dato regresado por la regla expresión devolverá un atributo llamado valor. ésto es también para las demás reglas que tengan la sentencia returns. * e1=mult {$valor = $e1.valor } , Al mismo tiempo, también podemos colocar “alias” a los nombres de los terminales y no terminales que estemos utilizando en la producción, en este caso el e1=mult indica que el símbolo mult, estará identificado dentro de las acciones como e1 para la accion que tenga más próxima, en este caso es {$valor = $e1.valor } , En esta acción también podemos ver que $valor nos representa el atributo del símbolo no terminal que identifica la produccción al cual queremos “pasar” la información. en este caso estaremos pasando el valor de e1, al atributo valor del símbolo suma . (OPSUM e2=mult { if ($OPSUM.getText() == '+') : $valor = $valor + $e2.valor if ($OPSUM.getText() == '-') : $valor = $valor - $e2.valor } //cerramos atributo )* Lo mismo que el anterior, le asignamos e2 al simbolo mult , Y como pueden darse cuenta ANTLR permite más de un segmento de codigo dentro de una regla gramatical, gracias a esto podemos evaluar cada repeticion que se encuentra en la sentencia mult (OPSUM mult)* , quedando como mult {} (OPSUM mult {})* en donde codigo2 se ejecutara cada vez que se lea OPSUM mult, pues esta dentro del operador de repeticion *. La producción mult es similar a la suma, con excepcion de que agrega una validación adicional dentro del código para divisiones entre cero. Debemos tener las siguientes consideraciones cuando creamos codigo en python. Podemos ver tambien que accedemos al valor del terminal OPSUM, con su atributo getText(), el cual devuelve el lexema leído actualmente por la regla OPSUM. con lo cual nos evitamos agregarles acciones semánticas a todos los terminales (ENTERO, NEWLINE, OPSUM, OPMUL).

Notas:

  • En las acciones, el codigo python, las sangrias deberan estar hechas por ESPACIOS, no por TABS.
  • Cuando se vaya a usar el operador % de python en las acciones, se debera anteceder con una backslash “" de esta manera a % b se convertiria en a \% b

Ya lista la gramática, generamos código de la siguiente manera:

Seleccionamos la opcion “generate code” del menu “Generate”, y si todo sale bien no dara ningun error, de otra manera ANTLRWorks avisara cualquier falta que se este cometiendo. El código será generado en un nuevo directorio llamado “output” dentro de nuestro DIRECTORIO DEL IDE.

Pero bueno… y como conecto lo generado con un script python?

Creamos el siguiente script que servirá como punto de entrada, para ello, estando en nuestro DIRECTORIO DEL IDE, nos metemos al directorio output generado por ANTLR al generar código, y abrimos el editor nano con $ cd output && nano ejemplo.py y escribimos lo siguiente import sys import antlr3 # importamos librerias de antlr3 from ejemploLexer import ejemploLexer # importamos nuestro lexer generado por ANTLRWorks from ejemploParser import ejemploParser # Importamos nuestro parser generado por ANTLRWorks inputf = file(“input.txt”) # leemos archivo de entrada char_stream = antlr3.ANTLRInputStream(inputf) #asignamos archivo de entrada a ANTLR lexer = ejemploLexer(char_stream) # Asignamos strean devuelto por ANTLR al lexer tokens = antlr3.CommonTokenStream(lexer) # Creamos stream de dokens con el lexer parser = ejemploParser(tokens) # Creamos nuevo parser y le pasamos el lexer de parametro parser.program() # Ejecutamos la produccion program del parser. inputf.close() # Cerramos archivo ya que todo se haya leido Nos salimos con CTRL+X y guardamos el script cuando nos lo pregunten.

Luego creamos el archivo de entrada

$ nano input.txt y escribimos en él 1*1 2*2 3*3 2*2/3 2*2/2 Nos salimos con CTRL+X y guardamos el archivo de entrada cuando nos lo pregunten. Corriendo el código: Lo ejecutamos con el siguiente comando python ejemplo.py y en la pantalla veremos los resultados. Los ejemplos pueden ser bajados directamente del siguiente URL:

http://rapidshare.com/files/215758595/ejemplos.tar.bz2

Siguiente –>