72 votes

Vous utilisez ANTLR 3.3 ?

J'essaie de me lancer dans l'utilisation d'ANTLR et de C#, mais je trouve cela extraordinairement difficile en raison du manque de documentation/tutoriels. J'ai trouvé quelques tutoriels peu convaincants pour les anciennes versions, mais il semble que l'API ait subi des modifications majeures depuis.

Quelqu'un peut-il me donner un exemple simple de la façon de créer une grammaire et de l'utiliser dans un programme court ?

J'ai finalement réussi à faire compiler mon fichier de grammaire en un lexer et un parser, et je peux les faire compiler et fonctionner dans Visual Studio (après avoir dû recompiler la source ANTLR parce que les binaires C# semblent être périmés aussi ! -- sans compter que la source ne compile pas sans quelques corrections), mais je n'ai toujours aucune idée de ce que je dois faire avec mes classes de parseur/lexer. Je suppose qu'il peut produire un AST à partir d'une certaine entrée... et je devrais être capable de faire quelque chose de fantaisiste avec ça.

132voto

Bart Kiers Points 79069

Disons que vous voulez analyser des expressions simples comprenant les mots suivants:

  • - de soustraction (également unaire);
  • + plus;
  • * la multiplication;
  • / de la division;
  • (...) groupement (sous -) expressions;
  • entiers et des nombres décimaux.

Une grammaire ANTLR pourrait ressembler à ceci:

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')+)?
  ;

Maintenant à la création d'un AST, vous ajoutez output=AST; votre options { ... } section, et vous mélangez de l'arborescence "opérateurs" dans votre grammaire de la définition de jetons doit être la racine d'un arbre. Il y a deux façons de le faire:

  1. ajouter ^ et ! après vos jetons. L' ^ provoque le jeton de devenir une racine et l' ! exclut le jeton de l'ast;
  2. à l'aide de "réécrire les règles": ... -> ^(Root Child Child ...).

Prendre la règle d' foo par exemple:

foo
  :  TokenA TokenB TokenC TokenD
  ;

et disons que vous souhaitez TokenB de devenir la racine et de l' TokenA et TokenC devenir ses enfants, et que vous souhaitez exclure TokenD de l'arbre. Voici comment le faire en utilisant l'option 1:

foo
  :  TokenA TokenB^ TokenC TokenD!
  ;

et voici comment le faire en utilisant l'option 2:

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

Alors, voici la grammaire avec l'arbre d'opérateurs:

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();}
  ;

J'ai également ajouté un Space règle ignorer les espaces blancs dans le fichier source et ajouté un peu plus de jetons et les espaces de noms pour l'analyseur lexical et l'analyseur. Notez que l'ordre est important (options { ... } d'abord, puis tokens { ... } et enfin l' @... {}-déclarations d'espace de noms).

C'est tout.

Maintenant générer un analyseur lexical et l'analyseur à partir de votre fichier de grammaire:

java -cp antlr-3.2.jar org.antlr.Outil D'Expression.g

et de mettre la .cs fichiers dans votre projet en collaboration avec le C# runtime DLL.

Vous pouvez le tester à l'aide de la classe suivante:

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);
    }
  }
}

qui produit la sortie suivante:

RACINE
*
+
12.5
/
56
UNARY_MIN
7
0.5

ce qui correspond à la suite de l'AST:

alt text

(diagramme créé à l'aide de graph.gafol.net)

Notez que ANTLR 3.3 vient de sortir et la CSharp cible est "beta". C'est pourquoi j'ai utilisé ANTLR 3.2 dans mon exemple.

En cas de plutôt simple langues (comme mon exemple ci-dessus), vous pouvez également évaluer le résultat à la volée sans pour autant créer un AST. Vous pouvez le faire que par l'incorporation de la plaine de code C# à l'intérieur de votre fichier de grammaire, et de laisser votre analyseur règles de retourner une valeur spécifique.

Voici un exemple:

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();}
  ;

qui peut être testé avec la classe:

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());
    }
  }
}

et produit la sortie suivante:

(12.5 + 56 / -7) * 0.5 = 2.25

MODIFIER

Dans les commentaires, Ralph a écrit:

Astuce pour ceux qui utilisent Visual Studio: vous pouvez mettre quelque chose comme java -cp "$(ProjectDir)antlr-3.2.jar" org.antlr.Tool "$(ProjectDir)Expression.g" dans la pré-construire des événements, alors vous pouvez simplement modifier votre grammaire et d'exécuter le projet sans avoir à vous soucier de la reconstruction de l'analyseur lexical/parser.

13voto

Toad Points 7868

Avez-vous regardé Ironie.net ? Il est destiné à .Net et fonctionne donc très bien, dispose d'un outil approprié, d'exemples appropriés et fonctionne tout simplement. Le seul problème est qu'il est encore un peu "alpha" et que la documentation et les versions semblent changer un peu, mais si vous vous en tenez à une version, vous pouvez faire des choses intéressantes.

p.s. désolé pour la mauvaise réponse où vous posez un problème sur X et quelqu'un suggère quelque chose de différent en utilisant Y ;^)

8voto

Lex Li Points 18214

Mon expérience personnelle est qu'avant d'apprendre ANTLR sur C#/.NET, vous devriez consacrer suffisamment de temps à apprendre ANTLR sur Java. Cela vous donne des connaissances sur tous les blocs de construction et plus tard vous pouvez appliquer sur C#/.NET.

J'ai écrit quelques articles de blog récemment,

L'hypothèse est que vous êtes familier avec ANTLR sur Java et que vous êtes prêt à migrer votre fichier de grammaire vers C#/.NET.

4voto

GreyCloud Points 1521

Il existe un excellent article sur la façon d'utiliser antlr et C# ensemble ici :

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

Il s'agit d'un article intitulé "How it was done" rédigé par le créateur de NCalc, un évaluateur d'expression mathématique pour C#. http://ncalc.codeplex.com

Vous pouvez également télécharger la grammaire pour NCalc ici : http://ncalc.codeplex.com/SourceControl/changeset/view/914d819f2865#Grammar%2fNCalc.g

exemple de fonctionnement de NCalc :

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()); 

J'espère que cela vous aidera

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X