web-dev-qa-db-fra.com

Vous utilisez ANTLR 3.3?

J'essaie de commencer avec ANTLR et C # mais je trouve cela extrêmement difficile en raison du manque de documentation/tutoriels. J'ai trouvé quelques didacticiels timides pour les anciennes versions, mais il semble qu'il y ait eu des changements majeurs dans l'API depuis.

Quelqu'un peut-il me donner un exemple simple de la création d'une grammaire et de son utilisation dans un programme court?

J'ai finalement réussi à compiler mon fichier de grammaire dans un lexer et un analyseur, et je peux les compiler et les exécuter dans Visual Studio (après avoir dû recompiler la source ANTLR parce que les binaires C # semblent également obsolètes! - sans oublier que la source ne se compile pas sans quelques correctifs), mais je n'ai toujours aucune idée de quoi faire avec mes classes analyseur/lexer. Soi-disant, il peut produire un AST étant donné une certaine entrée ... et alors je devrais être capable de faire quelque chose de compliqué avec ça.

72
mpen

Supposons que vous souhaitiez analyser des expressions simples constituées des jetons suivants:

  • - Soustraction (également unaire);
  • Ajout de +;
  • Multiplication de *;
  • Division /;
  • (...) Regroupant les (sous-) expressions;
  • nombres entiers et 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, pour créer un AST approprié, vous ajoutez output=AST; Dans votre section options { ... }, Et vous mélangez quelques "opérateurs d'arbre" dans votre grammaire définissant quels jetons doivent être la racine d'un arbre. Il y a deux façons de faire ça:

  1. ajoutez ^ et ! après vos jetons. Le ^ Fait que le jeton devient une racine et le ! Exclut le jeton de l'ast;
  2. en utilisant des "règles de réécriture": ... -> ^(Root Child Child ...).

Prenez la règle foo par exemple:

foo
  :  TokenA TokenB TokenC TokenD
  ;

et disons que vous voulez que TokenB devienne la racine et TokenA et TokenC pour devenir ses enfants, et que vous souhaitiez exclure TokenD de l'arborescence. Voici comment procéder à l'aide de l'option 1:

foo
  :  TokenA TokenB^ TokenC TokenD!
  ;

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

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

Alors, voici la grammaire avec les opérateurs d'arborescence:

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é une règle Space pour ignorer les espaces blancs dans le fichier source et ajouté des jetons et des espaces de noms supplémentaires pour le lexer et l'analyseur. Notez que l'ordre est important (options { ... } D'abord, puis tokens { ... } Et enfin @... {} - déclarations d'espace de noms).

C'est ça.

Maintenant, générez un lexer et un analyseur à partir de votre fichier de grammaire:

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

et placez les fichiers .cs dans votre projet avec les DLL d'exécution C # .

Vous pouvez le tester en utilisant 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 

qui correspond à l'AST suivant:

alt text

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

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

Dans le cas de langages assez simples (comme mon exemple ci-dessus), vous pouvez également évaluer le résultat à la volée sans créer d'AST. Vous pouvez le faire en incorporant du code C # simple dans votre fichier de grammaire et en laissant vos règles d'analyse renvoyer 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 

ÉDITER

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 les événements de pré-génération, puis vous pouvez simplement modifier votre grammaire et exécuter le projet sans avoir à vous soucier de reconstruire le lexer/analyseur.

132
Bart Kiers

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

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

13
Toad

Mon expérience personnelle est qu'avant d'apprendre ANTLR sur C # /. NET, vous devriez avoir suffisamment de temps pour 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 récemment écrit quelques articles de blog,

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

8
Lex Li

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

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

c'est un article "comment cela a été fait" par le créateur de NCalc qui est un évaluateur d'expression mathématique pour C # - http://ncalc.codeplex.com

Vous pouvez également télécharger la grammaire de 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 c'est utile

4
GreyCloud