自分で自分用のC#のソースコード編集ツールをつくったら捗りそうなことに気がつきました。 ググってみたら GitHub - dotnet/roslyn: The Roslyn .NET compiler provides C# and Visual Basic languages with rich code analysis APIs. というのでC#のソースコードのパースとコード生成が出来そうです。
c# - Edit loop in Roslyn - Stack Overflow で、それっぽいサンプルコードを見つけました。 試しにうごかしてみます。
まずは簡単そうなほうから試します。
// https://stackoverflow.com/questions/25568802/edit-loop-in-roslyn using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; SyntaxTree tree = CSharpSyntaxTree.ParseText( @"using System; using System.Collections.Generic; using System.Text; static void Main(string[] args) { for(int i=0; i<10; i++) int a = i; }"); var root = tree.GetRoot(); var forStmt = root.DescendantNodes().OfType<ForStatementSyntax>().Single(); var rewritten = root.ReplaceNode(forStmt, forStmt.WithCondition( SyntaxFactory.ParseExpression("i>=0") ).WithStatement( SyntaxFactory.ParseStatement(@" { Console.WriteLine(i); Console.WriteLine(i*2); } ") )); Console.WriteLine(rewritten);
動かすと次のように編集されたソースコードが表示されます。
勉強のために説明をしてみます。
SyntaxTree tree = CSharpSyntaxTree.ParseText( @"using System; using System.Collections.Generic; using System.Text; static void Main(string[] args) { for(int i=0; i<10; i++) int a = i; }");
var forStmt = root.DescendantNodes().OfType<ForStatementSyntax>().Single();
で構文木からfor文を表すノードを取得します。
var rewritten = root.ReplaceNode(forStmt, forStmt.WithCondition( SyntaxFactory.ParseExpression("i>=0") ).WithStatement( SyntaxFactory.ParseStatement(@" { Console.WriteLine(i); Console.WriteLine(i*2); } ") ));
で、for文を編集したものに置き換えます。 ここがややこしいです。
- 破壊的な変更ではない。もとのrootを変更するのではなくて、変更された新しい構文木が生成されます。
- 作りたい文にあわせて、良い感じのノードを作る
Console.WriteLine(rewritten);
で、構文木からコード生成します。 文字列化するだけで、コードが帰って来ます。
もう一つのサンプルも動作は同じです。
// https://stackoverflow.com/questions/25568802/edit-loop-in-roslyn using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; SyntaxTree tree = CSharpSyntaxTree.ParseText( @"using System; using System.Collections.Generic; using System.Text; static void Main(string[] args) { for(int i=0; i<10; i++) int a = i; }"); var root = tree.GetRoot(); var rewritten = new Rewriter().Visit(root); Console.WriteLine(rewritten); class Rewriter : CSharpSyntaxRewriter { public override SyntaxNode VisitForStatement(ForStatementSyntax node) { // update the current node with the new condition and statement return node.WithCondition( SyntaxFactory.ParseExpression("i>=0") ).WithStatement( SyntaxFactory.ParseStatement(@"{ Console.WriteLine(i); Console.WriteLine(i*2); }") ); } }
構文木を変更するややこしい部分を Rewriter クラスに分離しています。