使用ANTLR 3.3?

mpe*_*pen 72 c# parsing antlr

我正在尝试使用ANTLR和C#,但由于缺少文档/教程,我发现它非常困难.我找到了几个旧版本的半心半意的教程,但似乎从那时起对API进行了一些重大改动.

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

我终于设法将我的语法文件编译成词法分析器和解析器了,我可以在Visual Studio中编译和运行那些(在重新编译ANTLR源之后,因为C#二进制文件似乎也已经过时了! - 更不用说在没有一些修复的情况下源不会编译),但我仍然不知道如何处理我的解析器/词法分析器类.据说它可以在给出一些输入的情况下产生AST ......然后我应该可以做一些喜欢它的东西.

Bar*_*ers 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')+)?
  ;
Run Code Online (Sandbox Code Playgroud)

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

  1. 添加^和添加!您的令牌.的^原因令牌成为根和!排除从AST令牌;
  2. 通过使用"重写规则":... -> ^(Root Child Child ...).

以规则foo为例:

foo
  :  TokenA TokenB TokenC TokenD
  ;
Run Code Online (Sandbox Code Playgroud)

让我们说你想TokenB成为根,TokenATokenC成为它的孩子,你想要TokenD从树中排除.以下是使用选项1的方法:

foo
  :  TokenA TokenB^ TokenC TokenD!
  ;
Run Code Online (Sandbox Code Playgroud)

以下是使用选项2的方法:

foo
  :  TokenA TokenB TokenC TokenD -> ^(TokenB TokenA TokenC)
  ;
Run Code Online (Sandbox Code Playgroud)

所以,这里是树操作符的语法:

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();}
  ;
Run Code Online (Sandbox Code Playgroud)

我还添加了一条Space规则来忽略源文件中的任何空格,并为词法分析器和解析器添加了一些额外的标记和命名空间.请注意,顺序很重要(options { ... }首先,然后是tokens { ... }最后的@... {}-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);
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

产生以下输出:

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();}
  ;
Run Code Online (Sandbox Code Playgroud)

可以用班级测试:

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());
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

并产生以下输出:

(12.5 + 56 / -7) * 0.5 = 2.25

编辑

在评论中,拉尔夫写道:

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

  • 那些使用Visual Studio的人提示:你可以在预构建事件中添加类似`java -cp"$(ProjectDir)antlr-3.2.jar"org.antlr.Tool"$(ProjectDir)Expression.g"`然后你可以修改你的语法并运行项目,而不必担心重建词法分析器/解析器. (3认同)
  • 太好了!这是一个很大的帮助.我认为问题的很大一部分是3.3根本不起作用.它使`parse()`私有,而`skip()`不可用,而C#运行时不能使用它.这应该可以帮助我开始,多亏了! (2认同)

Toa*_*oad 13

你看过Irony.net了吗?它的目标是.Net,因此工作得很好,有适当的工具,适当的例子和工作.唯一的问题是它仍然有点'alpha-ish'所以文档和版本似乎有点改变,但如果你只是坚持一个版本,你可以做一些漂亮的事情.

ps对不起答案,你问一个关于X的问题,有人用Y建议不同的东西; ^)

  • 截至2014年,Irony已退居开发商的付薪职位. (2认同)

Lex*_* Li 8

我个人的经验是,在学习C#/ .NET上的ANTLR之前,你应该有足够的时间来学习Java上的ANTLR.这将为您提供所有构建块的知识,之后您可以在C#/ .NET上应用.

我最近写了一些博文,

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

  • 您不需要成为Java专家就可以完成Java教程.对我来说,它教会了我ANTLR的ABCs,然后我可以很快知道ANTLR如何工作并切换回C#. (3认同)