使用ANTLR 3.3?

我正在尝试开始使用ANTLR和C#,但是由于缺less文档/教程,我发现它非常困难。 我已经find了一些老版本的一些半心半意的教程,但似乎API已经有一些重大的改变。

任何人都可以给我一个简单的例子,如何创build一个语法,并在短程序中使用它?

我终于成功地将我的语法文件编译成一个词法分析器和parsing器,并且我可以在Visual Studio中获得这些编译和运行的语言(因为C#二进制文件似乎已经过时了,所以必须重新编译ANTLR源代码)更不用说源代码没有一些修正的情况下不能编译),但我仍然不知道如何处理我的分析器/词法分析器类。 据说,它可以产生一个AST给予一些input…然后我应该能够做一些事情与此。

假设您想分析由以下标记组成的简单expression式:

  • -减法(也是一元);
  • +另外;
  • *乘法;
  • /
  • (...)分组(子)expression式;
  • 整数和十进制数字。

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

现在创build一个合适的AST,你添加output=AST; 在您的options { ... }部分中,并且在语法中混合一些“树操作符”,定义哪些标记应该是树的根。 有两种方法可以做到这一点:

  1. ^! 在你的令牌之后。 ^使令牌成为根,而! 从ast中排除令牌;
  2. 通过使用“重写规则”: ... -> ^(Root Child Child ...)

foo的规则为例:

 foo : TokenA TokenB TokenC TokenD ; 

假设你想让TokenB成为根, TokenATokenC成为它的子TokenC ,并且你想从树中排除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 { ... } ,最后是@... {}名称空间声明)。

而已。

现在从您的语法文件中生成一个词法分析器和parsing器:

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

并将.cs文件与C#运行时DLL一起放入您的项目中。

你可以使用下面的类来testing它:

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

产生以下输出:

根
   *
     +
       12.5
       /
         56
         UNARY_MIN
           7
     0.5

它对应于下面的AST:

替代文字

(使用graph.gafol.net创build的图)

请注意,ANTLR 3.3刚刚发布,CSharp目标处于“testing阶段”。 这就是为什么我在我的例子中使用ANTLR 3.2的原因。

如果是相当简单的语言(就像我上面的例子),你也可以在不创buildAST的情况下即时评估结果。 你可以通过在你的语法文件中embedded简单的C#代码,并让你的parsing器规则返回一个特定的值。

这是一个例子:

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

可以用类来testing:

 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"的东西,然后就可以只需修改语法并运行该项目,而不必担心重build词法分析器/parsing器。

你看过Irony.net吗? 它针对.Net,因此工作得很好,有适当的工具,适当的例子,只是作品。 唯一的问题是,它仍然有点“阿尔法”,所以文档和版本似乎有所改变,但如果你只是坚持一个版本,你可以做的漂亮的事情。

ps对于你提出一个关于X的问题,并且有人用Y提出了一些不同的build议,对不起答案抱歉; ^)

我个人的经验是,在C#/ .NET上学习ANTLR之前,你应该有足够的时间学习Java上的ANTLR。 这给你所有的积木的知识,后来你可以应用在C#/。NET。

我最近写了一些博客post,

假设您熟悉Java上的ANTLR并准备将您的语法文件迁移到C#/ .NET。

有一篇关于如何在这里一起使用antlr和C#的好文章:

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

这是一个“如何完成”由NCalc的创build者,这是一个mathexpression式评估C#的文章 – 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()); 

希望它有帮助