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

kpo*_*zin 21 c# java parsing antlr

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

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

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

kpo*_*zin 19

我设法通过在Manuel Abadia的文章结尾处调整示例来解决这个问题.

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

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

要获取节点的文字文本,请使用node.Text.要获取节点的令牌名称,请使用node.Token.Text.

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

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

tokens { PROGRAM, FUNCDEC }

EQUALS : '==';
ASSIGN : '=';
Run Code Online (Sandbox Code Playgroud)

然后你将获得"PROGRAM","FUNCDEC""==",以及"="相应的访问node.Token.Text.

您可以在下面看到我的部分示例,或者您可以浏览完整版本.


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;

      ....
    }
}
Run Code Online (Sandbox Code Playgroud)


Bar*_*lly 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))
Run Code Online (Sandbox Code Playgroud)

处理的种类高度依赖于语言的语义.例如,在为JVM或CLR等堆栈机器生成代码时IF,使用结构处理语句(IF <predicate> <if-true> [<if-false>])可能看起来像这样:

func processIf(n)
    predicate = n.GetChild(0)
    processExpr(predicate) // get predicate value on stack
    falseLabel = createLabel()
    genCode(JUMP_IF_FALSE, falseLabel) // JUMP_IF_FALSE is called brfalse in CLR,
                                       // ifeq in JVM
    if_true = n.GetChild(1)
    processStmt(if_true)
    if_false = n.ChildCount > 2 ? n.GetChild(2) : null
    if (if_false != null)
        doneLabel = createLabel()
        genCode(JUMP, doneLabel)
    markLabel(falseLabel)
    if (if_false != null)
        processStmt(if_false) // if-false branch
        markLabel(doneLabel)
Run Code Online (Sandbox Code Playgroud)

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