Usando o ANTLR 3.3?

Eu estou tentando começar com ANTLR e C #, mas eu estou achando extraordinariamente difícil devido à falta de documentação / tutoriais. Eu encontrei alguns tutoriais para versões mais antigas, mas parece que houve algumas mudanças importantes na API desde então.

Alguém pode me dar um exemplo simples de como criar uma gramática e usá-lo em um programa curto?

Eu finalmente consegui obter o meu arquivo de gramática compilando em um lexer e parser, e posso obtê-los compilado e executando no Visual Studio (depois de ter que recompilar a fonte ANTLR porque os binários C # parecem estar desatualizados também! – para não mencionar a fonte não compila sem algumas correções), mas eu ainda não tenho idéia do que fazer com minhas classs parser / lexer. Supostamente, ele pode produzir uma AST com alguma contribuição … e então eu deveria ser capaz de fazer algo extravagante com isso.

Digamos que você queira analisar expressões simples que consistam nos seguintes tokens:

  • - subtração (também unária);
  • + adição;
  • * multiplicação;
  • / divisão;
  • (...) agrupando (sub) expressões;
  • números inteiros e decimais.

Uma gramática ANTLR poderia ser assim:

 grammar Expression; options { language=CSharp2; } parse : exp EOF ; exp : addExp ; addExp : mulExp (('+' | '-') mulExp)* ; mulExp : unaryExp (('*' | '/') unaryExp)* ; unaryExp : '-' atom | atom ; atom : Number | '(' exp ')' ; Number : ('0'..'9')+ ('.' ('0'..'9')+)? ; 

Agora, para criar um AST adequado, você adiciona output=AST; na sua seção de options { ... } , e você mistura alguns “operadores de tree” em sua gramática, definindo quais tokens devem ser a raiz de uma tree. Existem duas maneiras de fazer isso:

  1. adicione ^ e ! depois dos seus tokens. O ^ faz com que o token se torne uma raiz e o ! exclui o token do ast;
  2. usando “rewrite regras”: ... -> ^(Root Child Child ...) .

Tome a regra foo por exemplo:

 foo : TokenA TokenB TokenC TokenD ; 

e digamos que você queira que o TokenB se torne a raiz e o TokenA e o TokenC se tornem seus filhos, e você deseja excluir o TokenD da tree. Veja como fazer isso usando a opção 1:

 foo : TokenA TokenB^ TokenC TokenD! ; 

e aqui está como fazer isso usando a opção 2:

 foo : TokenA TokenB TokenC TokenD -> ^(TokenB TokenA TokenC) ; 

Então, aqui está a gramática com os operadores de tree:

 grammar Expression; options { language=CSharp2; output=AST; } tokens { ROOT; UNARY_MIN; } @parser::namespace { Demo.Antlr } @lexer::namespace { Demo.Antlr } parse : exp EOF -> ^(ROOT exp) ; exp : addExp ; addExp : mulExp (('+' | '-')^ mulExp)* ; mulExp : unaryExp (('*' | '/')^ unaryExp)* ; unaryExp : '-' atom -> ^(UNARY_MIN atom) | atom ; atom : Number | '(' exp ')' -> exp ; Number : ('0'..'9')+ ('.' ('0'..'9')+)? ; Space : (' ' | '\t' | '\r' | '\n'){Skip();} ; 

Eu também adicionei uma regra de Space para ignorar quaisquer espaços em branco no arquivo de origem e adicionei alguns tokens extras e namespaces para o lexer e parser. Note que a ordem é importante ( options { ... } primeiro, depois tokens { ... } e finalmente as declarações @... {} -namespace).

É isso aí.

Agora gere um lexer e um analisador de seu arquivo de gramática:

 java -cp antlr-3.2.jar org.antlr.Tool Expression.g

e coloque os arquivos .cs em seu projeto junto com as DLLs de runtime do C # .

Você pode testá-lo usando a seguinte class:

 using System; using Antlr.Runtime; using Antlr.Runtime.Tree; using Antlr.StringTemplate; namespace Demo.Antlr { class MainClass { public static void Preorder(ITree Tree, int Depth) { if(Tree == null) { return; } for (int i = 0; i < Depth; i++) { Console.Write(" "); } Console.WriteLine(Tree); Preorder(Tree.GetChild(0), Depth + 1); Preorder(Tree.GetChild(1), Depth + 1); } public static void Main (string[] args) { ANTLRStringStream Input = new ANTLRStringStream("(12.5 + 56 / -7) * 0.5"); ExpressionLexer Lexer = new ExpressionLexer(Input); CommonTokenStream Tokens = new CommonTokenStream(Lexer); ExpressionParser Parser = new ExpressionParser(Tokens); ExpressionParser.parse_return ParseReturn = Parser.parse(); CommonTree Tree = (CommonTree)ParseReturn.Tree; Preorder(Tree, 0); } } } 

que produz a seguinte saída:

 RAIZ
   *
     +
       12,5
       /
         56
         UNARY_MIN
           7
     0,5

que corresponde ao seguinte AST:

texto alternativo

(diagrama criado usando graph.gafol.net )

Observe que o ANTLR 3.3 acaba de ser lançado e o destino do CSharp está "em beta". É por isso que usei o ANTLR 3.2 no meu exemplo.

No caso de linguagens bastante simples (como o meu exemplo acima), você também pode avaliar o resultado sem criar um AST. Você pode fazer isso incorporando código C # simples dentro de seu arquivo de gramática e deixando que as regras do analisador retornem um valor específico.

Aqui está um exemplo:

 grammar Expression; options { language=CSharp2; } @parser::namespace { Demo.Antlr } @lexer::namespace { Demo.Antlr } parse returns [double value] : exp EOF {$value = $exp.value;} ; exp returns [double value] : addExp {$value = $addExp.value;} ; addExp returns [double value] : a=mulExp {$value = $a.value;} ( '+' b=mulExp {$value += $b.value;} | '-' b=mulExp {$value -= $b.value;} )* ; mulExp returns [double value] : a=unaryExp {$value = $a.value;} ( '*' b=unaryExp {$value *= $b.value;} | '/' b=unaryExp {$value /= $b.value;} )* ; unaryExp returns [double value] : '-' atom {$value = -1.0 * $atom.value;} | atom {$value = $atom.value;} ; atom returns [double value] : Number {$value = Double.Parse($Number.Text, CultureInfo.InvariantCulture);} | '(' exp ')' {$value = $exp.value;} ; Number : ('0'..'9')+ ('.' ('0'..'9')+)? ; Space : (' ' | '\t' | '\r' | '\n'){Skip();} ; 

que pode ser testado com a class:

 using System; using Antlr.Runtime; using Antlr.Runtime.Tree; using Antlr.StringTemplate; namespace Demo.Antlr { class MainClass { public static void Main (string[] args) { string expression = "(12.5 + 56 / -7) * 0.5"; ANTLRStringStream Input = new ANTLRStringStream(expression); ExpressionLexer Lexer = new ExpressionLexer(Input); CommonTokenStream Tokens = new CommonTokenStream(Lexer); ExpressionParser Parser = new ExpressionParser(Tokens); Console.WriteLine(expression + " = " + Parser.parse()); } } } 

e produz a seguinte saída:

 (12,5 + 56 / -7) * 0,5 = 2,25

EDITAR

Nos comentários, Ralph escreveu:

Dica para aqueles que usam o Visual Studio: você pode colocar algo como java -cp "$(ProjectDir)antlr-3.2.jar" org.antlr.Tool "$(ProjectDir)Expression.g" nos events de pré-compilation, então você pode apenas modifique sua gramática e execute o projeto sem ter que se preocupar em reconstruir o lexer / parser.

Você já olhou para o Irony.net ? É destinado a .Net e, portanto, funciona muito bem, tem ferramentas adequadas, exemplos adequados e apenas funciona. O único problema é que ainda é um pouco ‘alpha-ish’, então a documentação e as versões parecem mudar um pouco, mas se você ficar com uma versão, você pode fazer coisas bacanas.

ps desculpe pela má resposta onde você pergunta um problema sobre X e alguém sugere algo diferente usando Y; ^)

Minha experiência pessoal é que antes de aprender ANTLR em C # /. NET, você deve poupar tempo suficiente para aprender ANTLR em Java. Isso lhe dá conhecimento de todos os blocos de construção e depois você pode aplicar em C # / .net.

Eu escrevi alguns posts recentemente,

A suposição é que você esteja familiarizado com o ANTLR em Java e esteja pronto para migrar seu arquivo de gramática para C # /. NET.

Há um ótimo artigo sobre como usar antlr e c # juntos aqui:

http://www.codeproject.com/KB/recipes/sota_expression_evaluator.aspx

é um artigo “como foi feito” pelo criador do NCalc, que é um avaliador de expressões matemáticas para C # – http://ncalc.codeplex.com

Você também pode baixar a gramática do NCalc aqui: http://ncalc.codeplex.com/SourceControl/changeset/view/914d819f2865#Grammar%2fNCalc.g

exemplo de como o NCalc funciona:

 Expression e = new Expression("Round(Pow(Pi, 2) + Pow([Pi2], 2) + X, 2)"); e.Parameters["Pi2"] = new Expression("Pi * Pi"); e.Parameters["X"] = 10; e.EvaluateParameter += delegate(string name, ParameterArgs args) { if (name == "Pi") args.Result = 3.14; }; Debug.Assert(117.07 == e.Evaluate()); 

espero que seja útil