首页 文章

使用ANTLR 3.3?

提问于
浏览
71

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

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

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

4 回答

  • 8

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

    • - 减法(也是一元);

    • + 另外;

    • * 乘法;

    • / 师;

    • (...) 分组(子)表达式;

    • 整数和十进制数 .

    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; ,然后在语法中混合一些"tree operators",定义哪些标记应该是树的根 . 有两种方法可以做到这一点:

    • 在您的代币后添加 ^! . ^ 使令牌成为根, ! 从令牌中排除令牌;

    • 使用"rewrite rules": ... -> ^(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);
        }
      }
    }
    

    产生以下输出:

    ROOT
      *
        +
          12.5
          /
            56
            UNARY_MIN
              7
        0.5
    

    对应于以下AST:

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

    可以用 class 测试:

    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
    

    编辑

    在评论中,Ralph写道:对于那些使用Visual Studio的人:你可以把类似java -cp“$(ProjectDir)antlr-3.2.jar”org.antlr.Tool“$(ProjectDir)Expression.g”放在预构建事件,然后您可以修改您的语法并运行项目,而不必担心重建词法分析器/解析器 .

  • 4

    你看过Irony.net吗?它's aimed at .Net and therefore works really well, has proper tooling, proper examples and just works. The only problem is that it is still a bit ' alpha-ish'所以文档和版本似乎有点改变,但如果你只是坚持一个版本,你可以做一些漂亮的事情 .

    附:抱歉,你问一个关于X的问题,有人建议使用Y不同的东西; ^)

  • 132

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

    我最近写了一些博文,

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

  • 13

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

    http://www.codeproject.com/KB/recipes/sota_expression_evaluator.aspx

    这是NCalc创建者的一篇"how it was done"文章,它是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());
    

    希望它有所帮助

相关问题