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 eval
regla 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. ANTLRStringStream
necesitaba ser ANTLRInputStream
, el valor devuelto necesitaba cambiar de parser.eval()
a parser.eval().value
, y necesitaba eliminar la WS
cláusula al final, porque los valores de atributo como $channel
ya no pueden aparecer en acciones lexer.