web-dev-qa-db-ja.com

ANTLR 3.3を使用していますか?

ANTLRとC#を使い始めようとしていますが、ドキュメント/チュートリアルが不足しているため、非常に難しいと感じています。古いバージョン向けに中途半端なチュートリアルをいくつか見つけましたが、それ以来、APIに大きな変更が加えられているようです。

誰でも文法を作成して短いプログラムで使用する方法の簡単な例を教えてもらえますか?

最終的に文法ファイルをレクサーとパーサーにコンパイルし、Visual Studioでそれらをコンパイルして実行することができました(C#バイナリも古くなっているように見えるため、ANTLRソースを再コンパイルする必要があります!-ソースはいくつかの修正なしではコンパイルされないことは言うまでもありませんが)、パーサー/レクサークラスをどうするかはまだわかりません。おそらく、AST何らかの入力があれば...を生成できます。

72
mpen

次のトークンで構成される単純な式を解析するとします。

  • _-_減算(単項も);
  • _+_追加;
  • _*_乗算;
  • _/_部門;
  • _(...)_グループ化(サブ)式;
  • 整数および10進数。

ANTLR文法は次のようになります。

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

適切なASTを作成するには、_output=AST;_セクションに_options { ... }_を追加し、文法にいくつかの「ツリー演算子」を混在させて、どのトークンをツリーのルートにするかを定義します。これを行うには2つの方法があります。

  1. トークンの後に_^_および_!_を追加します。 _^_はトークンをルートにし、_!_はastからトークンを除外します。
  2. 「書き換えルール」を使用して:... -> ^(Root Child Child ...)

ルールfooを例に取ります:

_foo
  :  TokenA TokenB TokenC TokenD
  ;
_

そして、TokenBをルートにし、TokenATokenCをその子にし、TokenDをツリーから除外するとします。オプション1を使用してこれを行う方法は次のとおりです。

_foo
  :  TokenA TokenB^ TokenC TokenD!
  ;
_

オプション2を使用してこれを行う方法は次のとおりです。

_foo
  :  TokenA TokenB TokenC TokenD -> ^(TokenB TokenA TokenC)
  ;
_

そのため、ツリー演算子を含む文法は次のとおりです。

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

また、ソースファイル内の空白を無視するSpaceルールを追加し、レクサーとパーサーのトークンと名前空間をいくつか追加しました。順序が重要であることに注意してください(_options { ... }_最初に、_tokens { ... }_、最後に_@... {}_- namespace宣言)。

それでおしまい。

次に、文法ファイルからレクサーとパーサーを生成します。

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

そして、プロジェクトに_.cs_ファイルを C#ランタイムDLL's と一緒に配置します。

次のクラスを使用してテストできます。

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

次の出力が生成されます。

 ROOT 
 * 
 + 
 12.5 
 /
 56 
 UNARY_MIN 
 7 
 0.5 

次のASTに対応します。

alt text

graph.gafol.netを使用して作成された図

ANTLR 3.3がリリースされたばかりで、CSharpターゲットは「ベータ版」であることに注意してください。それが、私の例でANTLR 3.2を使用した理由です。

かなり単純な言語の場合(上記の私の例のように)、ASTを作成せずにその場で結果を評価することもできます。これを行うには、文法ファイル内にプレーンなC#コードを埋め込み、パーサールールに特定の値を返させます。

次に例を示します。

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

クラスでテストできます:

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

次の出力を生成します。

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

編集

コメントで、ラルフは次のように書いた:

Visual Studioを使用している場合のヒント:ビルド前イベントにJava -cp "$(ProjectDir)antlr-3.2.jar" org.antlr.Tool "$(ProjectDir)Expression.g"のようなものを入れることができます。その後、文法を変更し、レクサー/パーサーの再構築を心配せずにプロジェクトを実行できます。

132
Bart Kiers

Irony.net を見ましたか? .Netを対象としているため、非常にうまく機能し、適切なツール、適切な例があり、機能します。唯一の問題は、まだ少し「アルファっぽい」ということです。そのため、ドキュメントとバージョンは少し変更されているように見えますが、バージョンに固執するだけで、気の利いたことができます。

追伸Xについて問題を尋ね、Yを使用して別の何かを提案するという悪い答えをすみません; ^)

13
Toad

私の個人的な経験では、C#/。NETでANTLRを学習する前に、JavaでANTLRを学習するのに十分な時間を割く必要があります。これにより、すべての構成要素に関する知識が得られ、後でC#/。NETに適用できます。

最近いくつかのブログ記事を書きましたが、

JavaのANTLRに精通しており、文法ファイルをC#/。NETに移行する準備ができていることを前提としています。

8
Lex Li

ここにantlrとC#を一緒に使用する方法に関する素晴らしい記事があります:

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

これは、C#の数式評価ツールであるNCalcの作成者による「方法」の記事です。 http://ncalc.codeplex.com

NCalcの文法はこちらからダウンロードすることもできます。 http://ncalc.codeplex.com/SourceControl/changeset/view/914d819f2865#Grammar%2fNCalc.g

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

その役に立つことを願って

4
GreyCloud