C#でANTLRで生成されたASTを歩くためのチュートリアルを知っている人はいますか?私が見つけた最も近いものはこれですが、それはひどく役に立ちません。
私の目標は、作業中のドメイン固有言語に基づいて生成しているツリーをウォークスルーし、ツリーを使用して生成されたC#コードを出力することです。
Javaベースのチュートリアルも役立ちます-ANTLRASTをトラバースする方法の明確な例を提供するものなら何でも。
Manuel Abadiaの記事の最後にある例を採用することで、これを理解することができました。
これが私のバージョンです。これは、解析されたコードをC#に変換するために使用しています。手順は次のとおりです。
CommonTreeます。ノードのリテラルテキストを取得するには、を使用します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;
....
}
}
通常、再帰を使用して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に、構造体を使用してステートメントを処理すると(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)
通常、現在のノードのタイプなどに応じて、すべてが再帰的に実行されます。
私は似たようなことをしましたが(実際にはそうではありませんでした)、TreeParserに行き着きました。
また、ANTLRの本を購入することをお勧めします。私はそれがどのウェブリソースよりも価値があることを発見しました。それはすべての答えを持っているわけではないかもしれませんが、それは確かに基本に役立ちます。