在C#中行走ANTLR AST的教程?

2022-09-02 01:27:06

有没有人知道在C#中行走ANTLR生成的AST的教程?我能找到的最接近的是这个,但它并不是很有帮助。

我的目标是遍历基于我正在处理的特定于域的语言生成的树,并使用这些树输出生成的 C# 代码。

基于Java的教程也会有所帮助 - 任何提供如何遍历ANTLR AST的清晰示例的东西。


答案 1

我通过改编曼努埃尔·阿巴迪亚(Manuel Abadia)文章末尾的例子来解决这个问题。

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

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

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

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

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

tokens { PROGRAM, FUNCDEC }

EQUALS : '==';
ASSIGN : '=';

然后,您将从 的相应访问中获得 、 、 和。"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;

      ....
    }
}

答案 2

通常,您使用递归来遍历 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(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)

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


推荐