2009-05-20 14 views
21

ANTLRで生成されたASTをC#でウォーキングするためのチュートリアルを知っている人はいますか?私が見つけた最も近いものはthisですが、それはあまり役に立ちません。C#でANTLR ASTを歩くチュートリアル?

私の目標は、私が作業しているドメイン固有の言語に基づいて生成しているツリーを歩き回り、生成されたC#コードを出力するためにツリーを使用することです。

ANTLR ASTをトラバースする方法の明確な例を提供するJavaベースのチュートリアルも役立ちます。

答えて

19

Manuel Abadia's articleの最後に例を適用してこれを把握することができました。

ここに私のバージョンがあります。これは、解析されたコードをC#に変換するために使用されています。 これらは、手順は次のとおりです。

  1. は、(それがファイルまたは文字列を指定できます)あなたの入力とANTLRStringStreamまたはサブクラスをインスタンス化します。
  2. 生成されたレクサーをインスタンス化し、その文字列ストリームを渡します。
  3. トークンストリームをレクサーでインスタンス化します。
  4. パーサーをそのトークンストリームでインスタンス化します。
  5. パーサーから最上位の値を取得し、CommonTreeに変換します。ツリートラバース

ノードのリテラル文字列を取得するには、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; 

     .... 
    } 
} 
8

通常、ASTを再帰で処理し、ノードの種類に基づいて異なる処理を実行します。多相ツリーノード(ツリー内の異なるノードに異なるサブクラス)を使用している場合は、Visitorパターンでの二重ディスパッチが適切な場合があります。しかし、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 <predicate> <if-true> [<if-false>])で、多少のようになります、IF文を扱う:

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) 

することは一般的にすべてが、現在のタイプに応じて再帰的に行われていますノードなど

+0

C#のための木文法のいずれかのサンプルコード? – Kiquenet

0

私は(実際にはありません)似た何かをしたと私はTreeParserになってしまいました。

また、ANTLR本を購入することをお勧めします。私はそれがどんなWebリソースよりも貴重であることが分かった。それはすべての答えを持っていないかもしれませんが、それは確かに基本的に役立ちます。