ANTLR: ¿Hay un ejemplo simple?

230

Me gustaría comenzar con ANTLR, pero después de pasar unas horas revisando los ejemplos en el sitio antlr.org , todavía no puedo entender claramente el proceso de gramática en Java.

¿Hay algún ejemplo simple, algo así como una calculadora de cuatro operaciones implementada con ANTLR pasando por la definición del analizador y hasta el código fuente de Java?

Eli
fuente
2
Ese ejemplo preciso se usa como tutorial en el sitio de Antlr, la última vez que lo verifiqué.
Cory Petosky
1
@Cory Petosky: ¿puede suministrar el enlace?
Eli
Acabo de publicar las primeras partes de un video tutorial en ANTLR. Ver javadude.com/articles/antlr3xtut espero que les sea útil!
Scott Stanchfield
2
Yo también comparto tu búsqueda.
Paul Draper
1
La mejor respuesta para ANTLR 4 es comprar el libro de Parr "The Definitive ANTLR 4 Reference".
james.garriss

Respuestas:

448

Nota : ¡esta respuesta es para ANTLR3 ! Si está buscando un ejemplo ANTLR4 , estas preguntas y respuestas demuestran cómo crear un analizador de expresiones simple y un evaluador utilizando ANTLR4 .


Primero creas una gramática. A continuación hay una pequeña gramática que puede usar para evaluar las expresiones que se crean utilizando los 4 operadores matemáticos básicos: +, -, * y /. También puede agrupar expresiones usando paréntesis.

Tenga en cuenta que esta gramática es muy básica: no maneja operadores unarios (el menos en: -1 + 9) o decimales como .99 (sin un número inicial), por nombrar solo dos deficiencias. Este es solo un ejemplo en el que puede trabajar usted mismo.

Aquí está el contenido del archivo de gramática Exp.g :

grammar Exp;

/* This will be the entry point of our parser. */
eval
    :    additionExp
    ;

/* Addition and subtraction have the lowest precedence. */
additionExp
    :    multiplyExp 
         ( '+' multiplyExp 
         | '-' multiplyExp
         )* 
    ;

/* Multiplication and division have a higher precedence. */
multiplyExp
    :    atomExp
         ( '*' atomExp 
         | '/' atomExp
         )* 
    ;

/* An expression atom is the smallest part of an expression: a number. Or 
   when we encounter parenthesis, we're making a recursive call back to the
   rule 'additionExp'. As you can see, an 'atomExp' has the highest precedence. */
atomExp
    :    Number
    |    '(' additionExp ')'
    ;

/* A number: can be an integer value, or a decimal value */
Number
    :    ('0'..'9')+ ('.' ('0'..'9')+)?
    ;

/* We're going to ignore all white space characters */
WS  
    :   (' ' | '\t' | '\r'| '\n') {$channel=HIDDEN;}
    ;

(Las reglas del analizador comienzan con una letra minúscula y las reglas del lexer comienzan con una letra mayúscula)

Después de crear la gramática, querrás generar un analizador y un lexer a partir de ella. Descargue el jar ANTLR y guárdelo en el mismo directorio que su archivo de gramática.

Ejecute el siguiente comando en su shell / símbolo del sistema:

java -cp antlr-3.2.jar org.antlr.Tool Exp.g

No debería producir ningún mensaje de error, y los archivos ExpLexer.java , ExpParser.java y Exp.tokens ahora deberían generarse.

Para ver si todo funciona correctamente, cree esta clase de prueba:

import org.antlr.runtime.*;

public class ANTLRDemo {
    public static void main(String[] args) throws Exception {
        ANTLRStringStream in = new ANTLRStringStream("12*(5-6)");
        ExpLexer lexer = new ExpLexer(in);
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        ExpParser parser = new ExpParser(tokens);
        parser.eval();
    }
}

y compilarlo:

// *nix/MacOS
javac -cp .:antlr-3.2.jar ANTLRDemo.java

// Windows
javac -cp .;antlr-3.2.jar ANTLRDemo.java

y luego ejecutarlo:

// *nix/MacOS
java -cp .:antlr-3.2.jar ANTLRDemo

// Windows
java -cp .;antlr-3.2.jar ANTLRDemo

Si todo va bien, no se imprime nada en la consola. Esto significa que el analizador no encontró ningún error. Cuando se cambia "12*(5-6)"en "12*(5-6"y luego volver a compilar y ejecutarlo, no debe ser impresa la siguiente:

line 0:-1 mismatched input '<EOF>' expecting ')'

Bien, ahora queremos agregar un poco de código Java a la gramática para que el analizador realmente haga algo útil. La adición de código se puede hacer colocando {y }dentro de su gramática con algún código Java simple dentro de él.

Pero primero: todas las reglas del analizador en el archivo de gramática deben devolver un valor doble primitivo. Puede hacerlo agregando returns [double value]después de cada regla:

grammar Exp;

eval returns [double value]
    :    additionExp
    ;

additionExp returns [double value]
    :    multiplyExp 
         ( '+' multiplyExp 
         | '-' multiplyExp
         )* 
    ;

// ...

que necesita poca explicación: se espera que cada regla devuelva un valor doble. Ahora, para "interactuar" con el valor de retorno double value(que NO está dentro de un bloque de código Java simple {...}) desde el interior de un bloque de código, deberá agregar un signo de dólar delante de value:

grammar Exp;

/* This will be the entry point of our parser. */
eval returns [double value]                                                  
    :    additionExp { /* plain code block! */ System.out.println("value equals: "+$value); }
    ;

// ...

Aquí está la gramática pero ahora con el código Java agregado:

grammar Exp;

eval returns [double value]
    :    exp=additionExp {$value = $exp.value;}
    ;

additionExp returns [double value]
    :    m1=multiplyExp       {$value =  $m1.value;} 
         ( '+' m2=multiplyExp {$value += $m2.value;} 
         | '-' m2=multiplyExp {$value -= $m2.value;}
         )* 
    ;

multiplyExp returns [double value]
    :    a1=atomExp       {$value =  $a1.value;}
         ( '*' a2=atomExp {$value *= $a2.value;} 
         | '/' a2=atomExp {$value /= $a2.value;}
         )* 
    ;

atomExp returns [double value]
    :    n=Number                {$value = Double.parseDouble($n.text);}
    |    '(' exp=additionExp ')' {$value = $exp.value;}
    ;

Number
    :    ('0'..'9')+ ('.' ('0'..'9')+)?
    ;

WS  
    :   (' ' | '\t' | '\r'| '\n') {$channel=HIDDEN;}
    ;

y como nuestra evalregla ahora devuelve un doble, cambie su ANTLRDemo.java por esto:

import org.antlr.runtime.*;

public class ANTLRDemo {
    public static void main(String[] args) throws Exception {
        ANTLRStringStream in = new ANTLRStringStream("12*(5-6)");
        ExpLexer lexer = new ExpLexer(in);
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        ExpParser parser = new ExpParser(tokens);
        System.out.println(parser.eval()); // print the value
    }
}

Nuevamente (re) genere un nuevo lexer y analizador de su gramática (1), compile todas las clases (2) y ejecute ANTLRDemo (3):

// *nix/MacOS
java -cp antlr-3.2.jar org.antlr.Tool Exp.g   // 1
javac -cp .:antlr-3.2.jar ANTLRDemo.java      // 2
java -cp .:antlr-3.2.jar ANTLRDemo            // 3

// Windows
java -cp antlr-3.2.jar org.antlr.Tool Exp.g   // 1
javac -cp .;antlr-3.2.jar ANTLRDemo.java      // 2
java -cp .;antlr-3.2.jar ANTLRDemo            // 3

¡y ahora verá el resultado de la expresión 12*(5-6)impresa en su consola!

De nuevo: esta es una explicación muy breve. Te animo a navegar por el wiki de ANTLR y leer algunos tutoriales y / o jugar un poco con lo que acabo de publicar.

¡Buena suerte!

EDITAR:

Esta publicación muestra cómo extender el ejemplo anterior para que Map<String, Double>se pueda proporcionar un archivo que contenga variables en la expresión proporcionada.

Para que este código funcione con una versión actual de Antlr (junio de 2014), necesitaba hacer algunos cambios. ANTLRStringStreamnecesitaba ser ANTLRInputStream, el valor devuelto necesitaba cambiar de parser.eval()a parser.eval().value, y necesitaba eliminar la WScláusula al final, porque los valores de atributo como $channelya no pueden aparecer en acciones lexer.

Bart Kiers
fuente
1
¿Dónde parser.eval()ocurren las implementaciones de ? ¡Eso no está claro AQUÍ ni en el Wiki ANTLR3!
1
@Jarrod, err, lo siento, realmente no te entiendo. evales una regla de analizador que devuelve a double. Entonces, hay un eval()método al que puede llamar en una instancia de un ExpParser, tal como lo demostré en el ANTLRDemo.main(...). Después de generar un lexer / parser, simplemente abra el archivo ExpParser.javay verá que hay un eval()método que devuelve a double.
Bart Kiers
@Bart He estado investigando esto durante una semana: este es el primer ejemplo que fue realmente detallado y lo suficientemente completo como para funcionar la primera vez y creo que lo entiendo. Casi me había rendido. ¡Gracias!
Vineel Shah
13

El mega tutorial ANTLR de Gabriele Tomassetti es muy útil

Tiene ejemplos de gramática, ejemplos de visitantes en diferentes lenguajes (Java, JavaScript, C # y Python) y muchas otras cosas. Muy recomendable.

EDITAR: otros artículos útiles de Gabriele Tomassetti sobre ANTLR

solo
fuente
Gran tutorial!
Manish Patel
Antlr ahora tiene cpp también como idioma de destino. ¿Hay algún tutorial con ejemplo en cpp?
vineeshvs
El mismo tipo hizo un tutorial para ANTLR en C ++ tomassetti.me/getting-started-antlr-cpp. Supongo que vas a encontrar lo que estás buscando aquí o en el mega tutorial
Solo
7

Para Antlr 4, el proceso de generación de código java es el siguiente:

java -cp antlr-4.5.3-complete.jar org.antlr.v4.Tool Exp.g

Actualice su nombre de jar en classpath en consecuencia.

Abhishek K
fuente
2

En https://github.com/BITPlan/com.bitplan.antlr encontrará una biblioteca ANTLR de Java con algunas clases auxiliares útiles y algunos ejemplos completos. Está listo para usarse con maven y si te gusta eclipse y maven.

https://github.com/BITPlan/com.bitplan.antlr/blob/master/src/main/antlr4/com/bitplan/exp/Exp.g4

es un lenguaje de expresión simple que puede multiplicar y agregar operaciones. https://github.com/BITPlan/com.bitplan.antlr/blob/master/src/test/java/com/bitplan/antlr/TestExpParser.java tiene las pruebas unitarias correspondientes.

https://github.com/BITPlan/com.bitplan.antlr/blob/master/src/main/antlr4/com/bitplan/iri/IRIParser.g4 es un analizador IRI que se ha dividido en tres partes:

  1. gramática del analizador
  2. gramática lexer
  3. gramática LexBasic importada

https://github.com/BITPlan/com.bitplan.antlr/blob/master/src/test/java/com/bitplan/antlr/TestIRIParser.java tiene las pruebas unitarias para ello.

Personalmente, esta es la parte más difícil de entender. Ver http://wiki.bitplan.com/index.php/ANTLR_maven_plugin

https://github.com/BITPlan/com.bitplan.antlr/tree/master/src/main/antlr4/com/bitplan/expr

contiene tres ejemplos más que se han creado para un problema de rendimiento de ANTLR4 en una versión anterior. Mientras tanto, este problema se ha solucionado como muestra el caso de prueba https://github.com/BITPlan/com.bitplan.antlr/blob/master/src/test/java/com/bitplan/antlr/TestIssue994.java

Wolfgang Fahl
fuente
2

la versión 4.7.1 era ligeramente diferente: para importar:

import org.antlr.v4.runtime.*;

para el segmento principal - tenga en cuenta los CharStreams:

CharStream in = CharStreams.fromString("12*(5-6)");
ExpLexer lexer = new ExpLexer(in);
CommonTokenStream tokens = new CommonTokenStream(lexer);
ExpParser parser = new ExpParser(tokens);
usuario1562431
fuente