我正在尝试使用ANTLR和C#,但由于缺少文档/教程,我发现它非常困难。我找到了一些旧版本的半心半意的教程,但似乎API已经发生了一些重大变化。
有人能给我一个简单的例子来说明如何创建语法并在短程序中使用它吗?
我终于设法将我的语法文件编译成词法分析器和解析器,我可以在Visual Studio中编译和运行那些(在重新编译ANTLR源之后,因为C#二进制文件似乎已经过时了! - 更不用说源代码没有一些修复就没编译了),但我仍然不知道如何处理我的解析器/词法分析器类。据说它可以在给出一些输入的情况下产生AST ......然后我应该可以做一些奇特的事情。
答案 0 :(得分:132)
假设你要解析由以下标记组成的简单表达式:
-
减法(也是一元的); +
另外; *
乘法; /
分裂; (...)
分组(子)表达式; 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 { ... }
,然后在语法中混合一些“树操作符”,定义哪些标记应该是树的根。有两种方法可以做到这一点:
^
和!
。 ^
导致令牌成为根,!
从令牌中排除令牌; ... -> ^(Root Child Child ...)
。以规则foo
为例:
foo
: TokenA TokenB TokenC TokenD
;
并且假设您希望TokenB
成为根,TokenA
和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 { ... }
,最后是@... {}
- 名称空间声明。)
就是这样。
现在从语法文件中生成词法分析器和解析器:
java -cp antlr-3.2.jar org.antlr.Tool Expression.g
并将.cs
文件与C# runtime 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:
(使用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"
之类的内容,然后只需修改语法并运行项目,而无需担心重建词法分析器/解析器。 / p>
答案 1 :(得分:13)
你看过Irony.net了吗?它的目标是.Net,因此效果很好,有适当的工具,适当的例子和工作。唯一的问题是它仍然有点'alpha-ish'所以文档和版本似乎有所改变,但如果你只是坚持使用版本,你可以做一些漂亮的事情。
P.S。对于你问一个关于X的问题并且有人用Y建议不同的东西的答案很抱歉; ^)
答案 2 :(得分:8)
我个人的经验是,在学习C#/ .NET上的ANTLR之前,你应该有足够的时间来学习Java上的ANTLR。这将为您提供所有构建块的知识,之后您可以在C#/ .NET上应用。
我最近写了几篇博文,
假设您熟悉Java上的ANTLR并准备将语法文件迁移到C#/ .NET。
答案 3 :(得分:4)
这里有一篇关于如何一起使用antlr和C#的文章:
http://www.codeproject.com/KB/recipes/sota_expression_evaluator.aspx
这是NCalc创建者的“怎么做”的文章,它是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());
希望它有用