Compiler (ANTLR)
Compiler (ANTLR)
Generación de
Lexicográfico
fuente
Código
Semántico
Sintáctico
Caracteres Tokens AST AST
Análisis
Análisis
Análisis
código
…
Las dos primeras etapas de este proceso, pueden resultar bastante engorrosas si se
realizan a “mano” y en realidad son bastante similares aún cuando estemos
construyendo herramientas de procesamiento de lenguajes diferentes. Por ello es que
desde hace algún tiempo comenzaron a aparecer herramientas capaces de, dada cierta
especificación, generar código fuente eficiente que se encargue de la lógica que rodea
a los análisis lexicográfico y sintáctico.
ANTLR, como su propio nombre nos indica (ANother Tool for Language
Recognition) es una de estas herramientas, que en particular maneja la creación de
mecanismos de reconocimiento para gramáticas LL(k), en realidad pred-LL(k), pero
eso es tema para otra oportunidad.
Para ver los elementos más importantes en el uso de ANTLR, iremos construyendo
un pequeño evaluador de expresiones a partir de la gramática correspondiente:
E →T + E |T
T → F *T | F
F → n | (E)
donde n representa a todos los enteros no negativos.
ANTLR trabaja a partir de un archivo de texto que toma como entrada y que contiene
la información referente a nuestro lenguaje, los elementos lexicográficos, su
gramática además de un conjunto de opciones y especificaciones.
El archivo, es un fichero de texto, editado lo mismo en notepad, notepad++ o Visual
Studio, en nuestro caso le llamaremos expressions.g.
header
{
//Aquí ponemos el código que se incluirá al inicio de los
fuentes generados
//Fundamentalmente se usa para la inclusión de paquetes o
librerías del lenguaje
//en el que se va a generar el código
//Por ejemplo:
import os //Python
using System.Reflection; //C#
//Para nuestro ejemplo no será necesaria
Luego de esta, podemos incluir otra sección para especificar las opciones
generales de nuestro archivo, en particular el lenguaje que se ha de generar.
options
{
language = “CSharp”; //También “Java” que es el valor por
defecto, “Cpp”, “Python”
//Para nuestro ejemplo usaremos C#
namespace = “ANTLR_Example”
}
2. El Lexer
Pero lo podemos hacer mejor que eso, ANTLR incluye una construcción
cuya semántica indica tomar un
elemento dentro de un conjunto consecutivo de caracteres, nuestros enteros
quedarían:
N: (’0’..’9’)+ ;
N: (DIGIT)+;
4. Nuestro lexer
;
O bien pudiera quedar:
expression returns[int result = 0]
:
result = term
(
{
int t = 0;
}
t = term
{
result += t ;
}
)*
;
Nótese la forma de tomar el valor de retorno de una regla y de cómo las
variables e y t son solamente visibles desde el scope o ámbito de las
subreglas correspondientes.
Faltaría por ver cómo tomar el valor de un token, miremos la última regla
de nuestra gramática, escrita para nuestro evaluador en la sintaxis de
ANTLR:
3. Nuestro evaluador
if(args.Length>0)
{
using (StreamReader reader = new StreamReader(args[0]))
{
while(!reader.EndOfStream)
{
string line = reader.ReadLine();
StringReader lineReader = new StringReader(line);
ExpressionLexer lexer = new ExpressionLexer(lineReader);
ExpressionParser parser = new ExpressionParser(lexer);
Console.WriteLine(parser.expression());
}
}
Console.WriteLine("File ended");
Console.ReadLine();
Este código toma el archivo cuya ruta se le pasa como argumento y lo comienza a
leer línea por línea. Para cada línea crea un StringReader pues la clase Lexer
necesita un TextReader para y luego creamos nuestro Parser que necesita un
Lexer. Por último, imprimimos por la consola el resultado de llamar al método
expression que nos debe dar el resultado de evaluar la expresión escrita en la línea
correspondiente.
1+2
3*4
2*4+5
La salida es:
6. Conclusiones
ANTLR es hoy una de las herramientas generadoras de lexers y parsers más
populares, sin embargo, no es perfecta. Con ella nos limitamos a las gramáticas
LL(k), aunque tiene varias construcciones que nos hacen ir un poco más allá de
esta clase de lenguaje. No posee una interfaz agradable aunque hemos visto lo
fácil que es usarla desde Visual Studio y el código que genera abusa del
mecanismo de excepciones; pero no por esto deja de ser una muy buena
herramienta.
7. Ejercicios propuestos
1. Amplíe la gramática para evaluar expresiones con división y resta, así como
para enteros negativos.
2. Amplíe la gramática para “parsear” un archivo, como el archivo de entrada del
ejemplo, es decir un archivo donde en cada línea tenga una expresión. Su
evaluador debe imprimir por la consola el resultado de cada línea.
3. Haga un analizador sintáctico que elimine los paréntesis superfluos en una
expresión, reescriba la expresión con la menor cantidad de paréntesis posibles
sin que cambie la semántica y por ende su resultado.