22

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

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

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

4

4 回答 4

20

我设法通过改编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 : '=';

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

      ....
    }
}
于 2009-05-29T19:06:09.693 回答
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,使用 structure 处理语句,可能看起来有点像这样:(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)

一般来说,一切都是根据当前节点的类型递归完成的,等等。

于 2009-05-20T13:29:32.307 回答
1

You should look into writing a TreeParser; it can make the job of interpreting the tree much simpler.

For ANTLR 2.x see http://www.antlr2.org/doc/sor.html For ANTLR 3.x see http://www.antlr.org/wiki/display/ANTLR3/Tree+construction (java-based parser and tree parser example)

于 2009-05-20T14:17:07.420 回答
0

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

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

于 2009-05-20T14:24:43.740 回答