Nota : esta resposta é para ANTLR3 ! Se você está procurando um exemplo de ANTLR4 , estas perguntas e respostas demonstram como criar um analisador de expressão simples e um avaliador usando ANTLR4 .
Você primeiro cria uma gramática. Abaixo está uma pequena gramática que você pode usar para avaliar expressões criadas usando os 4 operadores matemáticos básicos: +, -, * e /. Você também pode agrupar expressões usando parênteses.
Observe que essa gramática é apenas muito básica: ela não lida com operadores unários (o menos em: -1 + 9) ou decimais como 0,99 (sem um número inicial), para citar apenas duas deficiências. Este é apenas um exemplo que você pode trabalhar em si mesmo.
Aqui está o conteúdo do arquivo 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;}
;
(As regras do analisador começam com uma letra minúscula e as regras do lexer começam com uma letra maiúscula)
Depois de criar a gramática, você desejará gerar um analisador e um lexer. Faça o download do jar ANTLR e armazene-o no mesmo diretório do seu arquivo de gramática.
Execute o seguinte comando no seu shell / prompt de comando:
java -cp antlr-3.2.jar org.antlr.Tool Exp.g
Ele não deve produzir nenhuma mensagem de erro e os arquivos ExpLexer.java , ExpParser.java e Exp.tokens agora devem ser gerados.
Para ver se tudo funciona corretamente, crie esta classe de teste:
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();
}
}
e compile-o:
// *nix/MacOS
javac -cp .:antlr-3.2.jar ANTLRDemo.java
// Windows
javac -cp .;antlr-3.2.jar ANTLRDemo.java
e execute-o:
// *nix/MacOS
java -cp .:antlr-3.2.jar ANTLRDemo
// Windows
java -cp .;antlr-3.2.jar ANTLRDemo
Se tudo correr bem, nada está sendo impresso no console. Isso significa que o analisador não encontrou nenhum erro. Quando você muda e, "12*(5-6)"
em "12*(5-6"
seguida, recompila e executa, deve ser impresso o seguinte:
line 0:-1 mismatched input '<EOF>' expecting ')'
Ok, agora queremos adicionar um pouco de código Java à gramática para que o analisador realmente faça algo útil. A adição de código pode ser feita colocando {
e }
dentro de sua gramática com algum código Java simples dentro dele.
Mas primeiro: todas as regras do analisador no arquivo de gramática devem retornar um valor duplo primitivo. Você pode fazer isso adicionando returns [double value]
após cada regra:
grammar Exp;
eval returns [double value]
: additionExp
;
additionExp returns [double value]
: multiplyExp
( '+' multiplyExp
| '-' multiplyExp
)*
;
// ...
que precisa de pouca explicação: espera-se que todas as regras retornem um valor duplo. Agora, para "interagir" com o valor de retorno double value
(que NÃO está dentro de um bloco de código Java simples {...}
) de dentro de um bloco de código, você precisará adicionar um cifrão na frente 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); }
;
// ...
Aqui está a gramática, mas agora com o código Java adicionado:
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;}
;
e como nossa eval
regra agora retorna um duplo, altere seu ANTLRDemo.java para este:
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
}
}
Novamente (re) gere um novo lexer e analisador da sua gramática (1), compile todas as classes (2) e execute 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
e agora você verá o resultado da expressão 12*(5-6)
impressa no seu console!
Novamente: esta é uma explicação muito breve. Convido você a navegar no wiki da ANTLR e ler alguns tutoriais e / ou brincar um pouco com o que acabei de publicar.
Boa sorte!
EDITAR:
Esta postagem mostra como estender o exemplo acima para que Map<String, Double>
seja possível fornecer um que retenha variáveis na expressão fornecida.
Para que esse código funcione com uma versão atual do Antlr (junho de 2014), eu precisava fazer algumas alterações. ANTLRStringStream
precisava se tornar ANTLRInputStream
, o valor retornado precisava mudar de parser.eval()
para parser.eval().value
e eu precisava remover a WS
cláusula no final, porque valores de atributo como $channel
não são mais permitidos para aparecer em ações lexer.