ANTLR: hay un ejemplo sencillo?


Me gustaría comenzar con ANTLR, pero después de pasar unas horas revisando los ejemplos en el antlr.org sitio, todavía no puedo obtener una comprensión clara de la gramática para el proceso de 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 todo el camino hasta el código fuente de Java?

Author: AlexP11223, 2009-12-19

4 answers

Primero creas una gramática. A continuación se muestra una pequeña gramática que puede usar para evaluar expresiones que se construyen 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 solo una 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 puedes trabajar.

Aquí está el contenido del archivo gramatical 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 analizador empiezan con una letra mayúscula)

Después de crear la gramática, querrá generar un analizador y un léxico a partir de ella. Descargue el ANTLR jar 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 debe producir ningún mensaje de error, y los archivos ExpLexer.java, ExpParser.java and Exp.ahora se deben generar tokens.

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 cambie "12*(5-6)" por "12*(5-6" y luego recompile y ejecute, debe imprimirse el 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 sintáctico 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 ella.

Pero primero: todas las reglas del analizador sintáctico en el archivo gramatical deben devolver un valor doble primitivo. Puedes hacerlo añadiendo 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 doble valor. Ahora para "interactuar" con el valor devuelto double value (que NO está dentro de un bloque de código Java simple {...}) desde dentro 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 añadido:

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 regla eval ahora devuelve un doble, cambie su ANTLRDemo.java en 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 léxico 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) impreso en su consola!

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

¡Buena suerte!

EDITAR:

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

Y este Q&Amuestra cómo crear un analizador de expresiones simple y un evaluador usando ANTLR4.

Para que este código funcione con una versión actual de Antlr (junio de 2014) necesitaba hacer algunos cambios. ANTLRStringStream necesitaba convertirse en ANTLRInputStream, el valor devuelto necesitaba cambiar de parser.eval() a parser.eval().value, y necesitaba eliminar la cláusula WS al final, porque los valores de atributos como $channel ya no se permiten que aparezcan en las acciones de lexer.

 416
Author: Bart Kiers,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:10:41

Para mí este tutorial fue muy útil: https://tomassetti.me/antlr-mega-tutorial

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

 8
Author: solo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-11-09 08:18:24

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 jar en classpath en consecuencia.

 7
Author: Abhishek K,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-04-16 06:48:13

En https://github.com/BITPlan/com.bitplan.antlr encontrará una biblioteca java ANTLR con algunas clases de ayuda útiles y algunos ejemplos completos. Está listo para ser utilizado 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 hacer multiplicar y sumar operación. 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 de IRI que se ha dividido en tres partes:

  1. gramática del analizador
  2. gramática léxica
  3. LexBasic importado gramática

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

Personalmente me pareció que esta es la parte más difícil de hacer bien. Véase 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 problema de rendimiento de ANTLR4 en una versión anterior. Mientras tanto, estos problemas se han solucionado como el testcase https://github.com/BITPlan/com.bitplan.antlr/blob/master/src/test/java/com/bitplan/antlr/TestIssue994.java muestra.

 1
Author: Wolfgang Fahl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-10-23 05:49:10