ANTLR:Existe um exemplo simples?
Pergunta
Gostaria de começar com o ANTLR, mas depois de passar algumas horas revisando os exemplos no antlr.org site, ainda não consigo entender claramente a gramática do processo Java.
Existe algum exemplo simples, algo como uma calculadora de quatro operações implementada com ANTLR passando pela definição do analisador até o código-fonte Java?
Solução
Observação:esta resposta é para ANTLR3!Se você está procurando um ANTLR4 exemplo, então isso Perguntas e respostas demonstra 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 construídas usando os 4 operadores matemáticos básicos:+, -, * e /.Você também pode agrupar expressões usando parênteses.
Observe que esta gramática é muito básica:ele não lida com operadores unários (o sinal de menos em:-1+9) ou decimais como 0,99 (sem número inicial), para citar apenas duas deficiências.Este é apenas um exemplo que você pode trabalhar sozinho.
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 a partir dela.Faça o download do Frasco ANTLR e armazene-o no mesmo diretório do seu arquivo de gramática.
Execute o seguinte comando em seu shell/prompt de comando:
java -cp antlr-3.2.jar org.antlr.Tool Exp.g
Não deve produzir nenhuma mensagem de erro e os arquivos ExpLexer.java, ExpParser.java e Exp.tokens agora deve ser gerado.
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 será impresso no console.Isso significa que o analisador não encontrou nenhum erro.Quando você muda "12*(5-6)"
em "12*(5-6"
e então recompilar e executá-lo, deverá 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.Adicionar código pode ser feito colocando {
e }
dentro da sua gramática com algum código Java simples dentro dela.
Mas primeiro:todas as regras do analisador no arquivo gramatical devem retornar um valor duplo primitivo.Você pode fazer isso adicionando returns [double value]
depois de 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 cada regra retorne 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 desde o nosso eval
regra agora retorna um duplo, mude seu ANTLRDemo.java para isto:
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 a partir de 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)
impresso em seu console!
De novo:esta é uma explicação muito breve.Eu encorajo você a navegar no Wiki ANTLR e leia alguns tutoriais e/ou brinque um pouco com o que acabei de postar.
Boa sorte!
EDITAR:
Esta postagem mostra como estender o exemplo acima para que um Map<String, Double>
pode ser fornecido que contenha variáveis na expressão fornecida.
Para que esse código funcionasse com uma versão atual do Antlr (junho de 2014), precisei fazer algumas alterações. ANTLRStringStream
precisava para se tornar ANTLRInputStream
, o valor retornado necessário para mudar de parser.eval()
para parser.eval().value
, e eu precisava remover o WS
cláusula no final, porque valores de atributos como $channel
não podem mais aparecer em ações lexer.
Outras dicas
Tutorial de mega mega antlr por gabriele tomassetti é muito útil
Possui exemplos gramaticais, exemplos de visitantes em diferentes idiomas (Java, JavaScript, C# e Python) e muitas outras coisas. Altamente recomendado.
EDIT: Outros artigos úteis de Gabriele Tomassetti no Antlr
Para Antlr 4, o processo de geração de código Java está abaixo:-
java -cp antlr-4.5.3-complete.jar org.antlr.v4.Tool Exp.g
Atualize seu nome de jarra no ClassPath de acordo.
No https://github.com/bitplan/com.bitplan.antlr Você encontrará uma biblioteca ANTLR Java com algumas classes de auxiliares úteis e alguns exemplos completos. Está pronto para ser usado com o Maven e se você gosta de eclipse e maven.
https://github.com/bitplan/com.bitplan.antlr/blob/master/src/main/antlr4/com/bitplan/exp/exp.g4
é uma linguagem de expressão simples que pode fazer multiplicar e adicionar operações.https://github.com/bitplan/com.bitplan.antlr/blob/master/src/test/java/com/bitplan/antlr/testexpparser.java tem os testes de unidade correspondentes para isso.
https://github.com/bitplan/com.bitplan.antlr/blob/master/src/main/antlr4/com/bitplan/iri/iriparser.g4 é um analisador IRI que foi dividido nas três partes:
- Gramática do analisador
- Gramática Lexer
- Gramática Lexbasica importada
https://github.com/bitplan/com.bitplan.antlr/blob/master/src/test/java/com/bitplan/antlr/testiriparser.javatem os testes de unidade para isso.
Pessoalmente, achei essa a parte mais complicada para acertar. 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
Contém mais três exemplos que foram criados para uma questão de desempenho do ANTLR4 em uma versão anterior. Enquanto isso, esses problemas foram corrigidos como o teste https://github.com/bitplan/com.bitplan.antlr/blob/master/src/test/java/com/bitplan/antlr/testissue994.java shows.
A versão 4.7.1 foi um pouco diferente: para importação:
import org.antlr.v4.runtime.*;
Para o segmento principal - observe os Charstreams:
CharStream in = CharStreams.fromString("12*(5-6)");
ExpLexer lexer = new ExpLexer(in);
CommonTokenStream tokens = new CommonTokenStream(lexer);
ExpParser parser = new ExpParser(tokens);