使用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 { ... }
部分中,并且在语法中混合一些“树操作符”,定义哪些标记应该是树的根。 有两种方法可以做到这一点:
- 加
^
和!
在你的令牌之后。^
使令牌成为根,而!
从ast中排除令牌; - 通过使用“重写规则”:
... -> ^(Root Child Child ...)
。
以foo
的规则为例:
foo : TokenA TokenB TokenC TokenD ;
假设你想让TokenB
成为根, TokenA
和TokenC
成为它的子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,
- http://www.lextm.com/index.php/2012/07/how-to-use-antlr-on-net-part-i/
- http://www.lextm.com/index.php/2012/07/how-to-use-antlr-on-net-part-ii/
- http://www.lextm.com/index.php/2012/07/how-to-use-antlr-on-net-part-iii/
- http://www.lextm.com/index.php/2012/07/how-to-use-antlr-on-net-part-iv/
- http://www.lextm.com/index.php/2012/07/how-to-use-antlr-on-net-part-v/
假设您熟悉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());
希望它有帮助