【问题标题】:Using ANTLR 3.3?使用 ANTLR 3.3?
【发布时间】:2011-05-22 17:13:36
【问题描述】:

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

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

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

【问题讨论】:

    标签: c# parsing antlr


    【解决方案1】:

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

    附言很抱歉你问了一个关于 X 的问题,有人用 Y 提出了不同的建议;^)

    【讨论】:

    • Hrm...一般情况下我会因为你忽略了这个问题而感到生气,但在我初步探讨时,这个讽刺实际上看起来相当不错:)
    • @Ralph,如果有人发布的答案没有 100% 解决您的问题,您真的“生气”了吗?
    • @Bart:我不确定“冒犯”是否正是我正在寻找的词......但是[通常]当有人发布完全不同的东西时,我觉得有点烦人,尤其是当我已经考虑过并决定反对它时。
    • @Ralph,是的,我可以想象你的情绪,但就个人而言,我不会使用“冒犯”这个词,仅此而已。并不是说蟾蜍的回答完全离题了。
    • 自 2014 年以来,Irony 已退居开发人员的付费工作之后。
    【解决方案2】:

    这里有一篇关于如何同时使用 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()); 
    

    希望对你有帮助

    【讨论】:

      【解决方案3】:

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

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

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

      就是这样。

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

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

      产生以下输出:

      根 * + 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

      编辑

      在 cmets 中,Ralph 写道:

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

      【讨论】:

      • 不错!这是一个很大的帮助。我认为问题的很大一部分是 3.3 根本不起作用。它使parse() 私有,而skip() 不可用,并且C# 运行时不能使用它。这应该可以帮助我开始,非常感谢!
      • 另外...你用什么程序来制作图表?看起来不错:)
      • 使用 Visual Studio 的提示:您可以在预构建事件中添加 java -cp "$(ProjectDir)antlr-3.2.jar" org.antlr.Tool "$(ProjectDir)Expression.g" 之类的内容 :) 然后您可以修改语法并运行项目,而不必担心重建词法分析器/解析器。
      • 我在使用最新的 antlr 运行时 (v3.4) 运行示例时遇到问题。使用语言=CSharp3;似乎可以解决它。
      • 是的,语言=CSharp3。此外,“parse”似乎没有显示为一种方法,除非它在 ​​.g 文件中以“public”开头,例如“startProg”,这里是:programming-pages.com/2012/07/01/…
      【解决方案4】:

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

      我最近写了几篇博文,

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

      【讨论】:

      • 先学java有什么好处?怎么会更容易(除了有更多的教程)?特别是如果我的 java 不如我的 C# 强大?
      • 您无需成为 Java 专家即可完成 Java 教程。对我来说,它教会了我 ANTLR 的基础知识,然后我可以快速了解 ANTLR 的工作原理并切换回 C#。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多