使用ANTLR 3.3吗?


72

我正在尝试使用ANTLR和C#,但是由于缺乏文档/教程,我发现它异常困难。我已经找到了一些旧版本的三心二意的教程,但此后似乎对API进行了一些重大更改。

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

我终于设法将语法文件编译成词法分析器和解析器,并且可以在Visual Studio中编译并运行这些文件(必须重新编译ANTLR源代码,因为C#二进制文件似乎也已经过时了!-更不用说没有一些修复,源代码也无法编译),但是我仍然不知道如何处理我的解析器/词法分析器类。假设在输入一些信息的情况下它可以产生AST ...然后我应该能够对此进行一些幻想。

Answers:


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

  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 { ... }@... {}-namespace声明,然后是-namespace声明)。

而已。

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

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

产生以下输出:

根
  *
    +
      12.5
      /
        56
        UNARY_MIN
          7
    0.5

对应于以下AST:

替代文字

(使用graph.gafol.net创建的图表

请注意,ANTLR 3.3刚刚发布,并且CSharp目标是“处于beta中”。这就是为什么我在示例中使用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"在预生成事件中添加类似内容,然后可以修改语法并运行项目,而不必担心重建词法分析器/解析器。


2
真好!这是一个很大的帮助。我认为问题的很大一部分是3.3根本不起作用。它是parse()私有的,skip()不可用,并且C#运行时无法使用它。这应该可以帮助我入门,非常感谢!
mpen 2010年

另外...您使用什么程序制作图表?看起来不错:)
mpen 2010年

3
对于使用Visual Studio的用户的提示:您可以java -cp "$(ProjectDir)antlr-3.2.jar" org.antlr.Tool "$(ProjectDir)Expression.g"在预生成事件中添加类似的内容:)然后,您可以修改语法并运行项目,而不必担心重建词法分析器/解析器。
mpen 2010年

1
我在使用最新的antlr运行时(v3.4)运行示例时遇到问题。使用language = CSharp3; 似乎解决了。
戴夫·特维

1
是的,语言= CSharp3。另外,“ parse”似乎没有显示为一种方法,除非它在.g文件中以“ public”开头,例如“ startProg”在此处:programming-pages.com/2012/07/01/…
贾里德

13

您看过Irony.net吗?它针对.Net,因此非常有效,具有适当的工具,适当的示例并且可以正常工作。唯一的问题是,它仍然有点“ alpha-ish”,因此文档和版本似乎有所变化,但是如果您坚持使用版本,则可以做一些漂亮的事情。

ps对于您回答有关X的问题的错误答案感到抱歉,有人使用Y提出了一些不同的建议; ^)


嗯...总的来说,我会因为您忽略了这个问题而感到生气,但是在我的初次戳戳过程中,这个讽刺实际上似乎非常好:)
mpen 2010年

@Ralph,如果有人发布的答案未100%解决您的问题,您真的“得罪了”吗?
巴特·基尔斯

@Bart:我不确定“得罪”是否正是我要寻找的词...但是[通常]当有人发布完全不同的内容时,尤其是当我已经考虑过时,我发现它有点烦人,并决定反对。
mpen 2010年

@Ralph,是的,我可以想象您的情绪,但是就我个人而言,我不会使用“冒犯”一词,仅此而已。并不是Toad的答案完全是题外话。
巴特·基尔斯

2
截至2014年,Irony已在开发商的薪酬工作中占了上风。
艾伦B

8

我的个人经验是,在C#/。NET上学习ANTLR之前,您应该花足够的时间在Java上学习ANTLR。这使您对所有构造块都有了了解,以后可以在C#/。NET上应用。

我最近写了一些博客文章,

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


1
首先学习Java的优势是什么?除了拥有更多教程之外,它会变得更容易吗?特别是如果我的Java不如我的C#强?
mpen 2012年

3
您无需成为Java专家即可完成Java教程。对我来说,它教会了我ANTLR的基础知识,然后我很快就能知道ANTLR的工作原理并切换回C#。
Lex Li

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

希望对您有所帮助

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.