Tutorial ANTLR
Tutorial ANTLR
Contenido
Tutorial ANTLR v4 .....................................................................................................................1
1. Antecedentes.....................................................................................................................2
2. Qu es ANTLR? .............................................................................................................3
3. rboles sintcticos en ANTLR v4....................................................................................4
4. Aplicaciones en el mundo actual ......................................................................................7
5. Instalacin .........................................................................................................................7
6. Estructura de un archivo fuente de ANTLR (especificacin de la gramtica) .................9
7. Reglas (analizadores lxicos y sintcticos) ....................................................................10
8. Salida ANTLR v4 ...........................................................................................................11
9. Probar el analizador generado ........................................................................................11
10. Ejemplos .......................................................................................................................16
11. Recursos adicionales (recomendados) ..........................................................................23
12. Referencias ...................................................................................................................23
1. Antecedentes
Ya sea para ensamblador, un lenguaje intermedio, o un lenguaje de programacin de alto nivel, al escribir un compilador
a mano debemos realizar muchas tareas de forma repetitiva. Todo este proceso se pude automatizar enormemente
utilizando herramientas de software.
Debido a la necesidad de automatizacin aparecieron, desde los aos 70, las primeras herramientas de ayuda a la
construccin de procesadores de lenguajes. Lo que hacen estas herramientas es generar cdigo en un lenguaje de
programacin (C, C++, JAVA, etc.) para ahorrar al programador la parte repetitiva de la programacin de compiladores,
pudiendo ste dedicarse al diseo del lenguaje y de las aplicaciones de procesamiento de lenguaje.
Varias universidades construyeron herramientas de este tipo, pero fueron YACC y LEX las que ms se han extendido. Al
principio de los 70, Stephen C. Johnson desarroll YACC (Yet Another Compiler-Compiler) laboratorios Bell, usando un
dialecto portable del lenguaje C. YACC es una herramienta capaz de generar un analizador sintctico en C a partir de una
serie de reglas de sintaxis que debe cumplir. Dichas reglas se especifican en un lenguaje muy sencillo. YACC se apoya en la
herramienta LEX para el anlisis lxico. LEX fue desarrollada por Eric Schmidt. Esta herramienta tambin fue desarrollada
en C, y tambin genera un analizador en C. LEX y YACC sirvieron como base a FLEX y BISON, que se consideran sus
herederas. FLEX y BISON son dos productos de la FSF (Free Software Foundation).
Ahora que el proceso de compilacin est ms formalizado; se admite ampliamente que es necesario crear un rbol de
sintaxis abstracta si se quiere realizar un anlisis semntico correctamente. Es necesario crear y recorrer de una forma
estandarizada los rboles de sintaxis abstracta.
ANTLR es un software desarrollado en JAVA por varios desarrolladores, aunque la idea inicial y las decisiones principales
de diseo son de Terence Parr. En su proyecto de grado, Terence presentaba una manera eficiente de implementar los
analizadores sintcticos LL. Los hallazgos presentados en este proyecto fueron los que le llevaron a implementar PCCTS,
que puede considerarse como la semilla de ANTLR. PCCTS permite generar analizadores lxicos y sintcticos. Para
recorrer los rboles de sintaxis abstracta, se desarroll un programa llamado SORCERER. ANTLR ha sufrido varias
reescrituras completas desde su inicio, incluyendo el cambio del lenguaje de programacin utilizado (inicialmente fue C) y
varios cambios de nombre. Mientras que FLEX y BISON son herramientas dedicadas a una sola fase del anlisis (anlisis
lxico y sintctico, respectivamente), ANTLR es capaz de actuar a tres niveles a la vez: anlisis lxico, sintctico y
semntico (cuatro si tenemos en cuenta la generacin de cdigo).
El uso de una sola herramienta para todos los niveles tiene varias ventajas. La ms importante es la estandarizacin:
con ANTLR basta con comprender el paradigma de anlisis una vez para poder implementar todas las fases de anlisis.
Con FLEX+BISON es necesario comprender y saber utilizar herramientas completamente diferentes. FLEX est basada en
autmatas finitos deterministas y BISON en un analizador LALR(1).
2. Qu es ANTLR?
ANTLR (ANother Tool for Language Recognition) es un generador de analizadores. Mucha gente llama a estas
herramientas compiladores de compiladores, dado que el ayudar a implementar compiladores es su uso ms popular.
ANTLR es una herramienta que integra la generacin de analizadores lxicos, sintcticos, rboles de sintaxis abstracta y
evaluadores de atributos. ANTLR est escrito en Java y genera: Java, C#, Javascript, Python2, Python3 (prximamente
C++). Todos los detalles se encuentran en su pgina oficial: http://www.antlr.org/
Su funcionamiento bsico es el siguiente:
Se disea un analizador lxico y un analizador sintctico usando una gramtica descrita en un archivo de
especificacin (escrito con la sintaxis propia de ANTLR).
ANTLR genera el cdigo fuente del analizador lxico y sintctico correspondientes.
Caractersticas:
Importante: ANTLR v4 genera analizadores LL(*) Adaptativos ALL(*). Gracias a esto, ANTLR v4 se ha llamado a ANTLR v4
como la versin Honey Badger en honor a ese curioso animal. It takes whatever grammar you give it; it doesnt give a
damn!
Ver video: The Crazy Nastyass Honey Badger - http://www.youtube.com/watch?v=4r7wHMg5Yjg
Observe que las hojas del rbol corresponden a nodos terminales y los dems nodos corresponden a alguna regla de
produccin de la gramtica. Estos ltimos objetos se conocen en ANTLR v4 como contextos (context) porque almacenan
todo lo que sabemos del reconocimiento de una frase por una regla particular de la gramtica. Cada contexto conoce su
token inicial y final para la frase y proporciona acceso a todos los elementos de esa frase. Por ejemplo, AssignContext
(Assign: ID TK_EQUAL expr) proporciona los mtodos: ID() y expr() para acceder al nodo identificador y el subrbol de la
expresin.
Con esta estructura de datos podramos implementar a mano nuestros algoritmos para recorrer el rbol sintctico en
profundidad y programar las acciones requeridas durante el anlisis, las cuales sern ejecutadas a medida que se vayan
visitando los nodos del rbol. Sin embargo, para evitar tener que escribir estos mtodos cada vez que implementemos un
procesador de lenguaje, ANTLR proporciona sus propios mecanismos para hacer esto por nosotros, mediante dos
patrones de diseo: Listeners y Visitors.
Parse-tree Listeners
Por defecto, ANTLR genera una interfaz para un parse-tree listener que responde a los eventos desencadenados por el
objeto que recorre el rbol sintctico (walker). Los Listeners son como los objetos manejadores de documentos SAX
(Simple API for XML) para los analizadores de XML. Estos reciben una notificacin de eventos como startDocument() y
endDocument() y ejecutan las acciones correspondiente. Los mtodos de un Listener son simplemente callbacks que
responden a eventos que ocurren al recorrer el rbol sintctico. Pueden ser comparados con los mtodos que
implementamos para responder a un evento de click en un botn de una aplicacin con interfaz grfica (GUI).
Para recorrer el rbol y por ende desencadenar las llamadas a los mtodos del Listener, ANTLR proporciona la clase
ParseTreeWalker. Para implementar una aplicacin, necesitamos desarrollar una implementacin de ParseTreeListener
que contenga el cdigo especfico de la aplicacin. ANTLR genera automticamente una subclase de ParseTreeListener
para cada gramtica especfica con los mtodos enter y exit para cada regla. Cuando el walker llega al nodo para la regla
Assign, se dispara el evento enterAssign(), el cual recibe como parmetro toda la informacin almacenada en ese
contexto (AssignContext). Asimismo, despus de que el walker ha visitado todos los hijos del nodo assign, se dispara el
evento exitAssign().
A continuacin se presenta toda la secuencia de mtodos desencadenados por el walker para la sentencia de ejemplo
(sp=100;):
Lo mejor de este mecanismo de Listeners es que todo funciona automticamente. No tenemos que implementar el
walker, y nuestros mtodos de los Listeners no tienen que visitar explcitamente a sus hijos. ANTLR proporciona esto.
Parse-tree Visitors
Sin embargo, en algunas situaciones queremos controlar el recorrido del rbol, llamando a los mtodos para visitar a los
hijos de algn nodo de forma explcita. En este caso debemos usar la opcin de ANTLR visitor en la lnea de
comandos. Esta opcin hace que ANTLR genere una interface de Visitor a partir de una gramtica, la cual contiene un
mtodo visit() por cada una de las reglas. A continuacin se puede ver el patrn de diseo visitor operando en nuestro
rbol sintctico:
Para dar inicio al recorrido del rbol, nuestra aplicacin crear una implementacin del visitor y llamar al mtodo visit()
de la raz del rbol (regla inicial de la gramtica).
Al hacer esto, ANTLR al ver la regla inicial de la gramtica, llamar al mtodo visitStat(). A partir de aqu, la
implementacin de visitStat(), llamar al mtodo visit() de los nodos hijos para continuar el recorrido del rbol. Tambin
se pueden llamar los mtodos explcitamente de la forma visitAssign().
Las bsquedas en Twitter usan ANTLR para parsear las consultas (ms de 2 millones de consultas al da).
Oracle utiliza ANTLR dentro del IDE SQL Developer y sus herramientas de migracin.
El IDE NetBeans analiza C++ con ANTLR.
Los lenguajes de Apache Hive, Apache Pig, y en general, los sistemas de bodegas de datos de Hadoop, todos
utilizan ANTLR.
Lex Machina1 utiliza ANTLR para la extraccin de informacin de textos legales.
El lenguaje HQL en el Hibernate object-relational mapping framework est construido con ANTLR.
Entorno de endurecimiento automtico de cdigo fuente SHE.
Muchos ms.
5. Instalacin
0.
1.
2.
3.
Si an no est instalada, instalar la mquina virtual de JAVA (JVM versin 1.6 o superior)
a. En la consola, verificar que funcionan los comandos: java y javac. Si no, incluir en la variable de
entorno PATH, el directorio donde estn dichos programas.
Descargar la versin ms reciente de ANTLR (Complete ANTLR 4.5.X Java binaries jar) desde
http://www.antlr.org/download.html
Guardar el archivo en una ubicacin conveniente, por ejemplo: C:\apps\ANTLR (sin espacios en blanco)
Aadir la ruta al CLASSPATH:
Windows:
a. Permanentemente:
Propiedades del sistema -> Variables de entorno -> Variables del sistema
->
CLASSPATH
->
Editar:
adicionar
.;C:\apps\ANTLR\antlr-4.5.2complete.jar (es necesario reiniciar la consola)
b.
Unix:
a. Ejecutar el siguiente comando desde consola o adicionarlo al script de inicio del shell (.bash_profile):
$ export CLASSPATH=".:/usr/local/lib/antlr-4.5.2-complete.jar:$CLASSPATH"
4.
Reglas sintcticas en EBNF (Extended Backus-Naur Form): Se permite +, *, ? en las partes derechas de las reglas.
Carrera 30 No. 45-03, FACULTAD DE INGENIERA, Edificio 453 Oficina 101,
Telefax: Conmutador: (57-1) 316 5000 Ext. 14011 Fax: 14014.
Correo electrnico: [email protected] / Bogot, Colombia, Sur Amrica
8. Salida ANTLR v4
Cuando ejecutamos ANTLR con el comando:
java org.antlr.v4.Tool PRUEBA.g4
Se crean como resultado los siguientes archivos (en el caso que se est usando JAVA):
HelloParser.java
HelloLexer.tokens
HelloBaseListener.java
Aunque los archivos que genera ANTLR v4 contienen todo lo necesario para ejecutar nuestro analizador, an no contamos
con un programa main que inicie el proceso de procesamiento del lenguaje dada una cadena/archivo de entrada.
Para probar los analizadores generados por ANTLR v4 primero que todo debemos compilar los fuentes generados (java):
>javac *.java
Esto nos genera los archivos *.class correspondientes y a partir de aqu tenemos 2 opciones: utilizar la herramienta de
prueba que proporciona ANTLR llamada TestRig; o desarrollar un programa main para integrar todo.
Opcin 1: TestRig
Es una herramienta flexible de prueba que ofrece ANTLR. De acuerdo a las opciones utilizadas puede mostrar informacin
til acerca del proceso de anlisis. Se puede ejecutar as:
>java org.antlr.v4.gui.TestRig Hello r opcin
Donde:
Hello: es el nombre de la gramtica a probar
r: es el smbolo inicial (la primer regla) de la gramtica
opcin: entre otras, puede ser:
o
tokens: si queremos que imprima los tokens que reconoce durante el anlisis de una cadena de
entrada. Ejemplo:
>java org.antlr.v4.gui.TestRig Hello r tokens
hello unal
EOF
A lo cual retorna:
[@0,0:4='hello',<1>,1:0]
[@1,6:9='unal',<2>,1:6]
[@2,12:11='<EOF>',<-1>,2:0]
Cada lnea de la salida representa la informacin conocida acerca de un token. Por ejemplo,
[@1,6:9='unal',<2>,1:6] indica que el token es el segundo (indexado desde 0 - @1), va
desde el carcter ubicado en la posicin 6 de la lnea hasta la 9 (inclusive y empezando en 0),
corresponde al lexema unal, tiene el token tipo 2 (ID), y est en la lnea 1 (contando desde 1) y en la
posicin 6 (empezando desde 0 y contando las tabulaciones como un solo carcter).
hello unal
EOF
A lo cual retorna:
(r hello unal)
o
gui: permite ver el rbol de derivacin grficamente. La forma ms fcil de verlo. Ejemplo:
>java org.antlr.v4.gui.TestRig Hello r gui
hello unal
EOF
A lo cual retorna:
Opcin 2: main
La segunda opcin, y la que utilizaremos despus de haber hecho las pruebas bsicas con TestRig, es realizar la
implementacin del mtodo main del analizador. A continuacin se muestra una plantilla para dicho mtodo. Reemplazar
NOMBRE_GRAMATICA y reglaInicialGramatica por los identificadores correspondientes:
// import de librerias de runtime de ANTLR
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.tree.*;
import java.io.File;
public class Test {
public static void main(String[] args) throws Exception {
try{
// crear un analizador lxico que se alimenta a partir de la entrada (archivo o consola)
NOMBRE_GRAMATICALexer lexer;
if (args.length>0)
lexer = new NOMBRE_GRAMATICALexer(new ANTLRFileStream(args[0]));
else
lexer = new NOMBRE_GRAMATICALexer(new ANTLRInputStream(System.in));
// Identificar al analizador lxico como fuente de tokens para el sintactico
CommonTokenStream tokens = new CommonTokenStream(lexer);
// Crear el analizador sintctico que se alimenta a partir del buffer de tokens
NOMBRE_GRAMATICAParser parser = new NOMBRE_GRAMATICAParser(tokens);
ParseTree tree = parser.reglaInicialGramatica(); // comienza el anlisis en la regla inicial
System.out.println(tree.toStringTree(parser)); // imprime el rbol de derivacin en forma textual
} catch (Exception e){
System.err.println("Error (Test): " + e);
}
}
}
Compilando una vez ms todos los archivo *.java, obtenemos el ejecutable de la clase Test (nuestro analizador). Se tiene
como resultado el analizador completo que admite un archivo de entrada desde la lnea de comandos, lo procesa y emite
los mensajes de error correspondientes (en caso que sea necesario).
Por ejemplo, para el caso de Hello.g4, el archivo Test.java contiene el main como se muestra a continuacin:
// import de librerias de runtime de ANTLR
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.tree.*;
import java.io.File;
public class Test {
public static void main(String[] args) throws Exception {
try{
//File f = new File(args[0]);
// crear un analizador lxico que se alimenta apartir de la entrada (archivo o consola)
HelloLexer lexer;
if (args.length>0)
lexer = new HelloLexer(new ANTLRFileStream(args[0]));
else
lexer = new HelloLexer(new ANTLRInputStream(System.in));
// Identificar al analizador lxico como fuente de tokens para el sintactico
CommonTokenStream tokens = new CommonTokenStream(lexer);
// Crear objeto del analizador sintctico que se alimenta apartir del buffer de tokens
HelloParser parser = new HelloParser(tokens);
ParseTree tree = parser.r(); // begin parsing at init rule
Suponiendo que Test.java est en el mismo directorio con los archivos generador por ANTLR
Se prueba:
>java Test input.in
Donde input.in es el archivo de prueba. En este caso contiene la cadena hello unal
(r hello unal)
10. Ejemplos
Ejemplo 1: Reconocedor de expresiones aritmticas - Expr (sin Listeners ni Visitors)
1.
Especificacin de la gramtica:
Archivo: Expr.g
grammar Expr;
// REGLAS SINTACTICAS
expr
: term ( (MAS | MENOS) term)*
{ System.out.println("Anlisis terminado.");
};
term
factor
: ENTERO;
// TOKENS
MAS
:
MENOS
:
MULT
:
DIV
:
'+';
'-';
'*';
'/';
// REGLAS LEXICAS
ENTERO : ('0'..'9')+;
ESPACIO : ( ' '
| '\t'
| '\r'
| '\n'
)+ -> channel(HIDDEN)
;
2.
3.
Programa main:
Archivo: MiExpr.java
// import de librerias de runtime de ANTLR
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.tree.*;
import java.io.File;
public class MiExpr {
public static void main(String[] args) throws Exception {
try{
// crear un analizador lxico que se alimenta a partir de la entrada (archivo o consola)
ExprLexer lexer;
if (args.length>0)
lexer = new ExprLexer(new ANTLRFileStream(args[0]));
else
lexer = new ExprLexer(new ANTLRInputStream(System.in));
// Identificar al analizador lxico como fuente de tokens para el sintactico
CommonTokenStream tokens = new CommonTokenStream(lexer);
// Crear el analizador sintctico que se alimenta a partir del buffer de tokens
ExprParser parser = new ExprParser(tokens);
ParseTree tree = parser.expr(); // comienza el anlisis en la regla inicial
System.out.println(tree.toStringTree(parser)); // imprime el rbol en forma textual
} catch (Exception e){
System.err.println("Error (Test): " + e);
}
}
}
4.
Compilar todo
>javac ExprLexer.java ExprParser.java MiExpr.java
5.
6.
Probar funcionamiento:
>java MiExpr entrada.txt
Anlisis terminado.
(expr (term (factor 23)) + (term (factor 3)) - (term (factor 9) * (factor 3)))
Especificacin de la gramtica:
Archivo: ArrayInit.g4
/** Grammars always start with a grammar header. This grammar is called
* ArrayInit and must match the filename: ArrayInit.g4
*/
grammar ArrayInit;
/** A rule called init that matches comma-separated values between {...}. */
init : '{' value (',' value)* '}' ; // must match at least one value
/** A value can be either a nested array/struct or a simple integer (INT) */
value : init
| INT
;
// parser rules start with lowercase letters, lexer rules with uppercase
INT : [0-9]+ ; // Define token INT as one or more digits
WS : [ \t\r\n]+ -> skip ; // Define whitespace rule, toss it out
;
Compilar todo
>javac *.java
Tree
>java org.antlr.v4.runtime.misc.TestRig ArrayInit init -tree
{99, 3, 491}
^Z
(init { (value 99) , (value 3) , (value 491) })
Desarrollar un traductor para este tipo de cadenas (usando Listeners): vamos a traducir los elementos de
inicializacin de un arreglo en Java como {99, 3, 451} (los cuales ya reconocemos) a una cadena de constantes
Unicode como: \u0063\u0003\u01c3, donde cada uno de estos corresponde a la notacin hexadecimal del valor
original (por ejemplo: 99d = 63h).
Anlisis previo:
Ejemplo de traduccin:
No es necesario sobreescribir todos los mtodos enter/exit, slo los que vamos a usar. La nica expresin poco
familiar en este ejemplo es ctx.INT(), la cual le solicita al objeto del contexto el token del nmero entero INT
(capturado por la regla value). A continuacin se presenta la implementacin del Listener de acuerdo a nuestras
reglas de traduccin.
Crear la aplicacin (traductor)
Slo nos falta crear la aplicacin como tal, basndonos en la clase Test mostrada previamente.
Archivo: Translate.java
// import ANTLR's runtime libraries
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.tree.*;
public class Translate {
public static void main(String[] args) throws Exception {
// create a CharStream that reads from standard input
ANTLRInputStream input = new ANTLRInputStream(System.in);
// create a lexer that feeds off of input CharStream
ArrayInitLexer lexer = new ArrayInitLexer(input);
// create a buffer of tokens pulled from the lexer
CommonTokenStream tokens = new CommonTokenStream(lexer);
// create a parser that feeds off the tokens buffer
ArrayInitParser parser = new ArrayInitParser(tokens);
ParseTree tree = parser.init(); // begin parsing at init rule
En este caso creamos un objeto walker de la clase ParseTreeWalker. Llamamos su mtodo walk(), el cual recorre el
rbol sintctico retornado por el parser. A medida que se va recorriendo el rbol, se van desencadenando los
llamados a los mtodos de nuestro Listener (ShortToUnicodeString).
Finalmente, compilamos y probamos el traductor
>javac ArrayInit*.java Translate.java
>java Translate
{99, 3, 451}
^Z
"\u0063\u0003\u01c3"
Ver la clase de Terence Parr (creador de ANTLR) en la Universidad de San Francisco, donde presenta ANTLR v4 explica Listeners y Visitors y los analizadores ALL(*). En este enlace: https://vimeo.com/59285751
12. Referencias
[1]
Parr Terence. The Definitive ANTLR 4 Reference. The pragmatic bookshelf. 2012.