ANTLR: Existe um exemplo simples?

230

Gostaria de começar a usar o ANTLR, mas depois de passar algumas horas analisando os exemplos no site antlr.org , 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 o ANTLR passando pela definição do analisador e todo o caminho até o código-fonte Java?

Eli
fonte
2
Esse exemplo preciso é usado como um tutorial no site do Antlr, a última vez que verifiquei.
Cory Petosky
1
@Cory Petosky: você pode fornecer o link?
Eli
Acabei de publicar as primeiras partes de um tutorial em vídeo no ANTLR. Veja javadude.com/articles/antlr3xtut Espero que seja útil!
21710 Scott Stanchfield
2
Eu também compartilho sua pesquisa.
Paul Draper
1
A melhor resposta para o ANTLR 4 é comprar o livro de Parr "The Definitive ANTLR 4 Reference".
James.garriss

Respostas:

448

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 evalregra 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. ANTLRStringStreamprecisava se tornar ANTLRInputStream, o valor retornado precisava mudar de parser.eval()para parser.eval().valuee eu precisava remover a WScláusula no final, porque valores de atributo como $channelnão são mais permitidos para aparecer em ações lexer.

Bart Kiers
fonte
1
Onde as implementações parser.eval()acontecem? Isso não está claro AQUI ou no ANTLR3 Wiki!
1
@ Jarrod, err, desculpe, eu realmente não entendo você. evalé uma regra do analisador que retorna a double. Portanto, há um eval()método que você pode chamar em uma instância de um ExpParser, assim como demonstrei no ANTLRDemo.main(...). Após gerar um lexer / analisador, basta abrir o arquivo ExpParser.javae você verá que existe um eval()método retornando a double.
Bart Kiers
@ Bart Estou pesquisando isso há uma semana - este é o primeiro exemplo que foi realmente detalhado e completo o suficiente para funcionar pela primeira vez e que acho que entendo. Eu quase desisti. Obrigado!
Vineel Shah
13

O mega tutorial da ANTLR de Gabriele Tomassetti é muito útil

Possui exemplos de gramática, exemplos de visitantes em diferentes linguagens (Java, JavaScript, C # e Python) e muitas outras coisas. Altamente recomendado.

EDIT: outros artigos úteis de Gabriele Tomassetti na ANTLR


fonte
Ótimo tutorial!
precisa
O Antlr agora tem o cpp também como idioma de destino. Existem tutoriais com exemplo no cpp?
vineeshvs
O mesmo cara fez um tutorial para ANTLR em C ++ tomassetti.me/getting-started-antlr-cpp Acho que você encontrará o que está procurando aqui ou no mega tutorial
solo
7

Para o 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 o nome do seu jar no caminho de classe adequadamente.

Abhishek K
fonte
2

Em https://github.com/BITPlan/com.bitplan.antlr, você encontrará uma biblioteca java ANTLR com algumas classes 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 multiplicar e adicionar operações. https://github.com/BITPlan/com.bitplan.antlr/blob/master/src/test/java/com/bitplan/antlr/TestExpParser.java possui os testes de unidade correspondentes.

https://github.com/BITPlan/com.bitplan.antlr/blob/master/src/main/antlr4/com/bitplan/iri/IRIParser.g4 é um analisador de IRI que foi dividido nas três partes:

  1. gramática do analisador
  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 possui os testes de unidade para isso.

Pessoalmente, achei a parte mais complicada de acertar. Consulte 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 um problema de desempenho do ANTLR4 em uma versão anterior. Enquanto isso, esses problemas foram corrigidos conforme mostra o testcase https://github.com/BITPlan/com.bitplan.antlr/blob/master/src/test/java/com/bitplan/antlr/TestIssue994.java .

Wolfgang Fahl
fonte
2

a versão 4.7.1 era 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);
user1562431
fonte