C# で ANTLR AST を実行するためのチュートリアル?
質問
ANTLR で生成された AST を C# で実行するためのチュートリアルを知っている人はいますか?私が見つけることができた中で最も近いのは、 これ, しかし、あまり役に立ちません。
私の目標は、開発中のドメイン固有言語に基づいて生成しているツリーをたどって、そのツリーを使用して生成された C# コードを出力することです。
Java ベースのチュートリアルも役に立ちます。ANTLR AST を走査する方法の明確な例を提供するものであれば何でも構いません。
解決
の最後にある例を適応させることで、これを理解することができました。 マヌエル・アバディアの記事.
これが私のバージョンです。解析されたコードを C# に変換するためにたまたま使用しています。手順は次のとおりです。
- インスタンス化する ANTLR文字列ストリーム または入力を使用してサブクラス化します (ファイルまたは文字列にすることができます)。
- 生成されたレクサーをインスタンス化し、その文字列ストリームを渡します。
- レクサーを使用してトークン ストリームをインスタンス化します。
- そのトークン ストリームを使用してパーサーをインスタンス化します。
- パーサーからトップレベルの値を取得し、それを
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 2.Xについては、 http://www.antlr2.org/doc/sorを見ます.htmlをする ANTLR 3.xのための参照 http://www.antlr.org/wiki/表示/ ANTLR3 /ツリー+建設する(Javaベースのパーサツリーパーサ例)
私は(実際にはありません)似た何かをしたと私はTreeParserになってしまいました。
私はまた、ANTLRの本を買ってお勧めします。私はそれが任意のWebリソースよりも貴重であることが判明しました。これは、すべての答えを持っていないかもしれないが、それは確かに基本的に役立ちます。