使用 ANTLR 3.3?

我正在尝试着开始使用 ANTLR 和 C # ,但是由于缺乏文档/教程,我发现这非常困难。我已经找到了一些老版本的教程,但是从那以后 API 似乎发生了一些重大的变化。

有人能给我一个简单的例子,说明如何创建一个语法并在一个简短的程序中使用它吗?

我终于成功地将我的语法文件编译成了 lexer 和 parser,并且我可以在 Visual Studio 中编译和运行这些文件(在重新编译 ANTLR 源代码之后,因为 C # 二进制文件似乎也过时了!——更不用说源代码在没有修复的情况下是不能编译的) ,但是我仍然不知道如何处理我的 parser/lexer 类。假设它可以产生一个 AST 给定一些输入... 然后我应该能够做一些花哨的东西。

23288 次浏览

你看过 Irony.net吗?它的目标是。网络,因此工作真的很好,有适当的工具,适当的例子,只是工程。唯一的问题是,它仍然有点“ alpha-ish”,所以文档和版本似乎有点变化,但如果您只是坚持使用一个版本,您可以做一些漂亮的事情。

附注: 很抱歉,当你问一个关于 X 的问题时,有人用 Y 给出了不同的建议; ^)

这里有一篇关于如何同时使用 antlr 和 C # 的文章:

Http://www.codeproject.com/kb/recipes/sota_expression_evaluator.aspx

这是一篇由 NCalc 的创建者撰写的“ How it was done”文章,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());

希望能有所帮助

假设您想解析由以下标记组成的简单表达式:

  • -减法(也是一元法) ;
  • 增加 +;
  • *增殖;
  • /分部;
  • (...)分组(子)表达式;
  • 整数和十进制数。

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,需要在 options { ... }部分中添加 output=AST;,并在定义哪些标记应该是树的根的语法中混合一些“树运算符”。有两种方法可以做到这一点:

  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规则来忽略源文件中的任何空白,并为 lexer 和解析器添加了一些额外的标记和名称空间。注意顺序很重要(先是 options { ... },然后是 tokens { ... },最后是 @... {}-名称空间声明)。

就是这样。

现在从语法文件中生成 lexer 和 parser:

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

并将 .cs文件与 C # 运行时 DLL 的文件放在一起。

您可以使用下面的类对其进行测试:

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

与以下数据相对应:

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"的内容,然后您可以修改您的语法并运行项目,而不必担心重新构建 lexer/解析器。

我个人的经验是在 C #/上学习 ANTLR 之前。NET,你应该腾出足够的时间来学习 ANTLR 在 Java 上。这让你了解所有的构建模块,之后你可以应用到 C #/。NET.

我最近写了几篇博文,

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