在C#中使用ANTLR ASTs的教程?

时间:2009-05-20 10:30:41

标签: c# java parsing antlr

是否有人知道在C#中使用ANTLR生成的AST的教程?我能找到的最接近的是this,但它并不是非常有用。

我的目标是根据我正在使用的特定于域的语言遍历我正在生成的树,并使用树来输出生成的C#代码。

基于Java的教程也很有帮助 - 任何提供如何遍历ANTLR AST的明确示例的内容。

4 个答案:

答案 0 :(得分:19)

我设法通过调整Manuel Abadia's article末尾的示例来解决这个问题。

这是我的版本,我碰巧用它来将解析后的代码转换为C#。 这些是步骤:

  1. 使用您的输入实例化ANTLRStringStream或子类(可以是文件或字符串)。
  2. 实例化生成的词法分析器,传入该字符串流。
  3. 使用词法分析器实例化令牌流。
  4. 使用该令牌流实例化您的解析器。
  5. 从解析器中获取顶级值,并将其转换为CommonTree
  6. 遍历树:
  7. 要获取节点的文字文本,请使用node.Text。 要获取节点的令牌名称,请使用node.Token.Text

    请注意,node.Token.Text只会为您提供令牌的实际名称,如果它是一个没有相应字符串的虚构标记。如果它是真正的令牌,则node.Token.Text将返回其字符串。

    例如,如果您的语法中包含以下内容:

    tokens { PROGRAM, FUNCDEC }
    
    EQUALS : '==';
    ASSIGN : '=';
    

    然后,您将从"PROGRAM"的相应访问中获得"FUNCDEC""==""="node.Token.Text

    您可以在下方看到我的部分示例,也可以浏览full version


    public static string Convert(string input)
    {
        ANTLRStringStream sStream = new ANTLRStringStream(input);
        MyGrammarLexer lexer = new MyGrammarLexer(sStream);
    
        CommonTokenStream tStream = new CommonTokenStream(lexer);
    
        MyGrammarParser parser = new MyGrammarParser (tStream);
        MyGrammarParser.program_return parserResult = parser.program();
    
        CommonTree ast = (CommonTree)parserResult.Tree;
    
        Print(ast);
        string output = header + body + footer;
    
        return output;
    }
    
    public static void PrintChildren(CT ast)
    {
        PrintChildren(ast, " ", true);
    }
    
    public static void PrintChildren(CT ast, string delim, bool final)
    {
        if (ast.Children == null)
        {
            return;
        }
    
        int num = ast.Children.Count;
    
        for (int i = 0; i < num; ++i)
        {
            CT d = (CT)(ast.Children[i]);
            Print(d);
            if (final || i < num - 1)
            {
                body += delim;
            }
        }
    }
    
    public static void Print(CommonTree ast)
    {
        switch (ast.Token.Text)
        {
            case "PROGRAM":
                //body += header;
                PrintChildren(ast);
                //body += footer;
                break;
            case "GLOBALS":
                body += "\r\n\r\n// GLOBALS\r\n";
                PrintChildren(ast);
                break;
            case "GLOBAL":
                body += "public static ";
                PrintChildren(ast);
                body += ";\r\n";
                break;
    
          ....
        }
    }
    

答案 1 :(得分:8)

通常,您使用递归来执行AST,并根据节点的类型执行不同的操作。如果您正在使用多态树节点(即树中不同节点的不同子类),那么访问者模式中的双重调度可能是合适的;但是,Antlr通常不太方便。

在伪代码中,步行通常看起来像这样:

func processTree(t)
    case t.Type of
        FOO: processFoo t
        BAR: processBar t
    end

// a post-order process
func processFoo(foo)
    // visit children
    for (i = 0; i < foo.ChildCount; ++i)
        processTree(foo.GetChild(i))
    // visit node
    do_stuff(foo.getText())

// a pre-order process
func processBoo(bar)
    // visit node
    do_stuff(bar.getText())
    // visit children
    for (i = 0; i < foo.ChildCount; ++i)
        processTree(foo.GetChild(i))

处理的种类高度依赖于语言的语义。例如,在为像JVM或CLR这样的堆栈计算机生成代码时,处理具有结构IF的{​​{1}}语句可能看起来像这样:

(IF <predicate> <if-true> [<if-false>])

通常一切都是递归完成的,具体取决于当前节点的类型等。

答案 2 :(得分:1)

你应该考虑编写一个TreeParser;它可以使解释树的工作变得更加简单。

对于ANTLR 2.x,请参阅http://www.antlr2.org/doc/sor.html 对于ANTLR 3.x,请参阅http://www.antlr.org/wiki/display/ANTLR3/Tree+construction(基于java的解析器和树解析器示例)

答案 3 :(得分:0)

我做了类似的事情(但不是真的),最后我得到了一个TreeParser。

我还建议购买ANTLR书。我发现它比任何网络资源都更有价值。它可能没有所有的答案,但它确实有助于基础。