LoginSignup
17
22

More than 3 years have passed since last update.

C# ソースコードのフローチャートを生成する(1)

Last updated at Posted at 2019-06-15

はじめに

コネクタが沢山あったり、ループの中にジャンプしたり、c言語でもめったに見ないようなフローをc#で実装することになりました。
何とか実装したのですが、実装したコードがフローと合っているか検証するため、Graphvizを生成するトレース用のコードを埋め込み、フローチャートの生成とトレース(カバレッジ)を行いました。
実はこの埋め込み作業を手作業でやったんです。
以下のような感じです。

Sample.cs
void CreateDot()
{
    CreateNoed("n0001","diamond","if(a)");
    CreateEdge("n0001","n0002","true");
    CreateNoed("n0002","rect","b = true;");
    CreateEdge("n0001","n0003","false");
    CreateNoed("n0003","rect","b = false;");
}
void TargetCode()
{
    TraceNode("n0001");
    if(a)
    {
        TraceNode("n0002");
        b = true;
    }else
    {
        TraceNode("n0003");
        b = false;
    }
}

出力結果は以下のようになります。トレースした場合通過した個所を赤、回数を括弧内に表示します。
syntax2.txt.png

これからが本題ですが、このコード生成を自動化して動作状況を可視化できないものかと考えています。
(1)ソースコードのフローチャートを生成する
 この記事です。
 コードはだらだら作っただけなので、また機会があれば見直したいと思います。
(2)実行結果をフローチャートでトレースする。
 上記TargetCodeの生成が前提となります。
 こちらは 「Roslyn の構文解析を使ってデバッガーを自作する」が非常に参考になります。
 できたも同然と思っていますがやっていません。そのうちやるかも知れないので今回の記事は「(1)」としました。
(3)実行箇所を画面で判るようにする。
 こちらは(2)のデバッガーでも良いのですが、実行箇所をSVGで描画したりブレークしたりする機能です。
 実験コードは作ったもののWPFコントロールにしたいとか余計なことを考えてしまって先に進んでいません。記事にする場合「(3)」になると思います。

ソースコードのフローチャートを作成する

Sample.csのCreatDotに相当する部分の実装です。
自分なりに調べた結果ですが、Microsoft.CodeAnalysis.CSharpを使えばソースコードをAST化して解析できることが判りました。
すべてコードのフローチャート化が目的ではないので、対応しないパターン沢山あります。また非同期処理の動作は無視されます。
テストプログラムでは以下の文を解析対象としています。

No. SyntaxNode 構文例 コメント
1 VariableDeclarationSyntax int a = 0;
2 IfStatementSyntax if(a){}
3 ForStatementSyntax for(){}
4 DoStatementSyntax do{}while()
5 SwitchStatementSyntax switch(i){}
6 SwitchStatementSyntax foreach(var a in x){}
7 WhileStatementSyntax while(b){}
8 GotoStatementSyntax goto L01;
9 TryStatementSyntax try{}catch{}
10 LabeledStatementSyntax L01:;
11 BreakStatementSyntax break;
12 ContinueStatementSyntax continue;
13 ExpressionStatementSyntax n++;
14 ReturnStatementSyntax return true;
15 UsingStatementSyntax using{}
16 ParenthesizedLambdaExpressionSyntax Task.Run(()=>{....}; 括弧付きラムダ式

テスト用のソース

以下のソースコードなどでフローチャートを作成してみました。
尚、コードの内容は意味を持ちません。

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace Samples
{
    class Source1
    {
        static void Main(string[] args)
        {
            // テスト用のソース
            int a = 0;    //aを初期化
            int b = 1000;   //bを初期化

            for (int i = 0; i < b; i++)
            {
                var xi = a * 2 + b;
                //最初のコメント

                /*次のコメント
                 *  改行1    

                 * 改行2*/
                //テスト分岐
                if (b == 100)
                //読み飛ばしたいコメント
                {
                    a = (a + 1);
                    goto EXT0;
                }
                if (b != xi)
                {
                    //aを1インクリメント
                    //加算
                    a = AddFunc(a, 1);
                    /*aを2インクリメント
                     * sss */
                    a = new NumConv(a).Add(2);
                }
                else { break; }
                b = xi;                
                var count = 0;
                try
                {
                    do
                    {
                        count++;
                    } while (count < 5);
                }
                catch
                {
                    return;
                }
                finally
                {
                    b = b + xi;
                }
            }
            EXT0:;
            Console.WriteLine(b);            
        }

        private static int AddFunc(int xx, int dx)
        {
            int x = dx;
            //swtch
            switch (x)
            {
                case 1:
                    x = 11;
                    break;
                case 2:
                    x = 12;
                    break;
                default:
                    break;
            }
            Task.Run(() =>
            {
                SleepFunc();
            });
            while (true)
            {
                x++;
                if (x < 10)
                    continue;
                return xx + dx;
            }

            void SleepFunc()
            {
                Thread.Sleep(1000);
                Console.WriteLine("sleep");
            }
        }

        private class NumConv
        {
            private static Dictionary<string, int> CONV = new Dictionary<string, int>() { { "A", 10 }, { "B", 11 } };
            private int IValue { get; set; }
            public NumConv(int value)
            {
                IValue = value;
                var list = new List<int>(CONV.Values);
                var result = list.Select((int x) => { return x + 1; });
                using (StreamWriter writer = new StreamWriter("test.txt", false))
                {
                    foreach (var intValue in result)
                    {
                        writer.WriteLine("intValue=" + intValue);
                    }
                }
            }
            public int Add(int dx)
            {
                return IValue + dx;
            }

        }
    }
}

フローチャート出力結果

ソースファイル単位にフローチャートを出力します。
制約はありますがコメントも反映します。
ローカル関数がどこに属するか現状判りませんがクラスタ化すれば良いと思います。
syntax2.txt.png

環境

開発環境
Visual Studio Community 2017
NuGetでMicrosoft.CodeAnalysis.CSharp3.1.0をインストール

ターゲットフレームワーク
.NET Fremawork 4.6.1

Graphviz
Graphviz2.38

変更履歴

  • 2019/6/25 SyntaxVisitor.cs一部修正

ソースコード

以下はc#のソースファイルを入力してdotファイルを出力するコンソールアプリです。

(1)テストプログラムメイン

Main.cs
using System.IO;
namespace Flow
{
    class FlowChartGen
    {
        //args[0]:対象c#ソースファイル , args[1]:dotファイル出力先
        static void Main(string[] args)
        {
            string sourceCode = File.ReadAllText(args[0]);
            FlowGraph graph = new FlowGraph(sourceCode);
            graph.WriteDot(args[1]);
        }
    }
}

(2)関数単位のパーサー

SyntaxVisitor.cs
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace Flow
{
    public class SyntaxVisitor : CSharpSyntaxWalker
    {
        private FlowGraph FlowGraph;
        private List<GraphNode> UpstreamNodes { get; set; } = new List<GraphNode>();
        private ScopeInfo<GraphNode> Scope;
        private SyntaxNode _root;
        public SyntaxVisitor(FlowGraph graph)
        {
            FlowGraph = graph;
            Scope = new ScopeInfo<GraphNode>();
        }

        public SyntaxVisitor(FlowGraph graph, ScopeInfo<GraphNode> scope)
        {
            FlowGraph = graph;
            Scope = scope;
        }

        public List<GraphNode> VisitSyntax(SyntaxNode root, List<GraphNode> upstreamNodes)
        {
            return VisitSyntax(root, upstreamNodes, "");
        }
        public List<GraphNode> VisitSyntax(SyntaxNode root, List<GraphNode> upstreamNodes, string edegLabel)
        {
            //想定としてupstreamNodesが複数要素になることは無いと思うがListとして扱う
            if (UpstreamNodes == null)
            {
                UpstreamNodes = new List<GraphNode>();
            }
            else
            {
                UpstreamNodes = new List<GraphNode>(upstreamNodes);
            }
            if (edegLabel.Length > 0)
            {
                foreach (GraphNode gNode in UpstreamNodes)
                {
                    gNode.TransitEdgeLabel = edegLabel;
                }
            }
            if (root is SyntaxNode)
            {
                base.Visit(root);
            }
            return UpstreamNodes;
        }

        public List<GraphNode> VisitRootSyntax(SyntaxNode root)
        {
            var property = root.GetType().GetProperty("Body");
            var body = (SyntaxNode)property.GetMethod.Invoke(root, new object[] {  });

            _root = root;
            GraphNode startNode = FlowGraph.CreateGraphNode(root, GetNodeLabeStatementPart(_root, false) + "\\nstart", "ellipse");
            VisitSyntax(body, new List<GraphNode>() { startNode });
            GraphNode endNode = FlowGraph.CreateGraphNode(body, "end", "ellipse");
            UpstreamNodes = ConnectUpstreamNodes(endNode, UpstreamNodes, Param.connect_ReturnStatement);
            return UpstreamNodes;
        }

        private void CreateEdge(GraphNode headNode)
        {
            if (UpstreamNodes != null)
            {
                foreach (GraphNode upstreamNode in UpstreamNodes)
                {
                    FlowGraph.AddEdge(upstreamNode, headNode, upstreamNode.TransitEdgeLabel);
                }
            }
        }

        private string GetDefaultNodeLabe(SyntaxNode node)
        {
            string commnet = GetNodeLabelCommentPart(node, true, false, true);
            return commnet + (commnet.Length > 0 ? "\\n" : "") + GetNodeLabeStatementPart(node, false);
        }

        private string GetNodeLabeStatementPart(SyntaxNode node, bool isAddKey = true)
        {
            string label;
            if (node is IfStatementSyntax ||
                node is ForStatementSyntax ||
                node is WhileStatementSyntax ||
                node is ForEachStatementSyntax ||
                node is SwitchStatementSyntax ||
                node is DoStatementSyntax)
            {
                var nodesTokens = node.DescendantNodesAndTokens(_ => true).Where(_ => _.IsToken);
                var nodeToken = nodesTokens.ElementAt(0);
                var parent = node.DescendantNodesAndTokens(_ => true).Where(_ => _.IsKind(SyntaxKind.OpenParenToken) || _.IsKind(SyntaxKind.CloseParenToken));
                int st = parent.First().SpanStart - node.SpanStart;
                int en = parent.Last().SpanStart - parent.First().SpanStart + 1;
                int bcount = 0;
                foreach (var p in parent)
                {
                    if (p.IsKind(SyntaxKind.OpenParenToken))
                    {
                        bcount++;
                    }
                    else if (p.IsKind(SyntaxKind.CloseParenToken))
                    {
                        bcount--;
                        if (bcount == 0)
                        {
                            en = p.SpanStart - parent.First().SpanStart + 1;
                            break;
                        }
                    }
                }
                label = nodeToken.ToString() + node.ToString().Substring(st, en);
            }
            else if (node is MethodDeclarationSyntax methodDecl)
            {
                label = methodDecl.Identifier.ToString();
            }
            else if (node is LocalFunctionStatementSyntax funcDecl)
            {
                label = funcDecl.Identifier.ToString();
            }
            else if (node is ConstructorDeclarationSyntax constructorDecl)
            {
                label = constructorDecl.Identifier.ToString();
            }
            else
            {
                label = node.ToString();
            }
            return (isAddKey ? node.Kind() + ":" : "") + label;
        }

        private string GetNodeLabelCommentPart(SyntaxNode node, bool singleLineComment, bool multiLineComment = false, bool narrowRange = true)
        {
            int endOfLine = 0;
            StringBuilder sb = new StringBuilder("");
            var nodesOrTokens = node.DescendantNodesAndTokens(_ => true).Where(_ => _.HasLeadingTrivia || _.IsToken);
            if (nodesOrTokens.Count() > 0)
            {
                var leadingTrvia = nodesOrTokens.First().GetLeadingTrivia();
                for (var i = leadingTrvia.Count - 1; i >= 0; i--)
                {
                    var tr = leadingTrvia[i];
                    if (tr.IsKind(SyntaxKind.EndOfLineTrivia))
                    {
                        if (narrowRange && endOfLine > 0)
                        {
                            break;
                        }
                        endOfLine++;
                    }
                    if ((singleLineComment && tr.Kind() == SyntaxKind.SingleLineCommentTrivia))
                    {
                        string str = leadingTrvia[i].ToString();
                        str = Regex.Replace(str, "^//[( |\t|)]*", "");
                        sb.Insert(0, str + (sb.Length > 0 ? "\\n" : ""));
                        endOfLine = 0;
                    }
                    if ((multiLineComment && tr.Kind() == SyntaxKind.MultiLineCommentTrivia))
                    {
                        string str = leadingTrvia[i].ToString();
                        string[] strLines = leadingTrvia[i].ToString().Split('\n');
                        for (var j = strLines.Length - 1; j >= 0; j--)
                        {
                            string strl = strLines[j];
                            if (j == strLines.Length - 1)
                            {
                                strl = Regex.Replace(strl, "[( |\t|)]*\\*/$", "");
                            }
                            if (j == 0)
                            {
                                strl = Regex.Replace(strl, "^/\\*[( |\t|)]*", "");
                            }
                            if (j > 0)
                            {
                                strl = Regex.Replace(strl, "^[( |\t|)]*\\*[( |\t|)]*", "");
                            }
                            sb.Insert(0, strl + (sb.Length > 0 ? "\\n" : ""));
                        }
                        endOfLine = 0;
                    }
                }
            }
            return sb.ToString();
        }

        private bool IsAncesterdNode(SyntaxNode cand, SyntaxNode breakNode)
        {
            foreach (var ancester in breakNode.Ancestors(true))
            {
                if (ancester == cand)
                {
                    return true;
                }
            }
            return false;
        }

        private bool IsAncesterHasSameMember(SyntaxNode root, SyntaxNode gotoNode)
        {
            foreach (var ancester in gotoNode.Ancestors(true))
            {
                if (ancester == root)
                {
                    return true;
                }
            }
            return false;
        }

        public override void DefaultVisit(SyntaxNode node)
        {
            base.DefaultVisit(node);
        }

        public override void VisitLocalFunctionStatement(LocalFunctionStatementSyntax node)
        {
            SyntaxVisitor ast = new SyntaxVisitor(FlowGraph);
            ast.VisitRootSyntax(node);
        }

        public override void VisitVariableDeclaration(VariableDeclarationSyntax node)
        {
            var gNode = CreateGraphNoed(node, Param.label_shortStatement);
            UpstreamNodes = ConnectUpstreamNodes(gNode, UpstreamNodes);
            base.DefaultVisit(node);
        }

        public override void VisitParenthesizedLambdaExpression(ParenthesizedLambdaExpressionSyntax node)
        {
            foreach (var ancester in node.Ancestors(true))
            {
                if (ancester is VariableDeclarationSyntax || ancester is ExpressionStatementSyntax)
                {
                    GraphNode ancGNode = FlowGraph.GetGraphNode(ancester);                    
                    if (ancGNode != null)
                    {
                        Regex re = new Regex("{.*}", RegexOptions.Singleline);
                        ancGNode.LabelName = re.Replace(ancGNode.LabelName, "{}");
                        ancGNode.NodeShape = "hexagon";
                    }
                }
            }
            UpstreamNodes = ConnectProgeny(node.Body, UpstreamNodes, "");
        }

        public override void VisitIfStatement(IfStatementSyntax node)
        {
            var gNode = CreateGraphNoed(node, Param.label_kindAndStatement, Param.shape_diamond);
            UpstreamNodes = ConnectUpstreamNodes(gNode, UpstreamNodes);
            List<GraphNode> destList = ConnectProgeny(node.Statement, UpstreamNodes, "true");
            destList.AddRange(ConnectProgeny(node.Else, UpstreamNodes, "false"));
            UpstreamNodes = destList;
        }

        public override void VisitForStatement(ForStatementSyntax node)
        {
            var gNode = CreateGraphNoed(node, Param.label_kindAndStatement, Param.shape_hexagon);
            List<GraphNode> destNoeds = ConnectProgeny(node.Statement, new List<GraphNode> { gNode }, "true");
            UpstreamNodes.AddRange(destNoeds);
            //ループ脱出
            gNode.TransitEdgeLabel = "false";
            UpstreamNodes = ConnectUpstreamNodes(gNode, UpstreamNodes, Param.connect_ContinueStatement, Param.join_BreakStatement);
        }

        public override void VisitDoStatement(DoStatementSyntax node)
        {
            //無理やりdoノードを表示する
            var doNode = CreateGraphNoed(node, "do", Param.shape_circle);
            var gNode = CreateGraphNoed(node.Condition, Param.label_statement, Param.shape_hexagon);
            UpstreamNodes.Add(gNode);
            UpstreamNodes = ConnectUpstreamNodes(doNode, UpstreamNodes);
            UpstreamNodes = ConnectProgeny(node.Statement, UpstreamNodes);
            UpstreamNodes = ConnectUpstreamNodes(gNode, UpstreamNodes, Param.shape_hexagon, Param.connect_ContinueStatement, Param.join_BreakStatement);

            //doノードを表示しない
            //var gNode = CreateGraphNoed(node, Param.expression_kindAndStatement, Param.shape_hexagon);
            //TailNodes.Add(gNode);
            //TailNodes = ConnectProgeny(node.Statement, TailNodes);
            //TailNodes = ConnectTailNodes(gNode, TailNodes, Param.shape_hexagon, Param.connect_ContinueStatement, Param.connect_BreakStatement);
        }

        public override void VisitSwitchStatement(SwitchStatementSyntax node)
        {
            var gNode = CreateGraphNoed(node, Param.label_kindAndStatement, Param.shape_hexagon);
            List<GraphNode> switchNode = new List<GraphNode>() { gNode };
            List<GraphNode> terminalNoedsList = new List<GraphNode>();
            foreach (var section in node.Sections)
            {
                terminalNoedsList.AddRange(ConnectProgeny(section, switchNode, section.Labels.ToString()));
            }
            terminalNoedsList.AddRange(ConnectUpstreamNodes(gNode, UpstreamNodes, Param.join_BreakStatement));
            //Switch文からのEdgeは無いはずなので削除します
            terminalNoedsList.RemoveAt(0);
            UpstreamNodes = terminalNoedsList;
        }

        public override void VisitForEachStatement(ForEachStatementSyntax node)
        {
            var gNode = CreateGraphNoed(node, Param.label_kindAndStatement, Param.shape_hexagon);
            List<GraphNode> destNoeds = ConnectProgeny(node.Statement, new List<GraphNode> { gNode });
            UpstreamNodes.AddRange(destNoeds);
            UpstreamNodes = ConnectUpstreamNodes(gNode, UpstreamNodes, Param.shape_hexagon, Param.connect_ContinueStatement, Param.join_BreakStatement);
        }

        public override void VisitWhileStatement(WhileStatementSyntax node)
        {
            var gNode = CreateGraphNoed(node, Param.label_kindAndStatement, Param.shape_hexagon);
            List<GraphNode> destNoeds = ConnectProgeny(node.Statement, new List<GraphNode> { gNode }, "true");
            UpstreamNodes.AddRange(destNoeds);
            gNode.TransitEdgeLabel = "false";
            UpstreamNodes = ConnectUpstreamNodes(gNode, UpstreamNodes, Param.shape_hexagon, Param.connect_ContinueStatement, Param.join_BreakStatement);
            if (node.Condition.Kind() == SyntaxKind.TrueLiteralExpression)
            {
                UpstreamNodes.Remove(gNode);
            }
        }

        public override void VisitGotoStatement(GotoStatementSyntax node)
        {
            var gNode = CreateGraphNoed(node);
            UpstreamNodes = ConnectUpstreamNodes(gNode, UpstreamNodes, Param.link_none);
        }

        public override void VisitTryStatement(TryStatementSyntax node)
        {
            var gNode = CreateGraphNoed(node, "Try", Param.shape_Mdiamond);
            UpstreamNodes = ConnectUpstreamNodes(gNode, UpstreamNodes);
            List<GraphNode> downstreamNodes = ConnectProgeny(node.Block, UpstreamNodes);
            gNode.TransitEdgeLabel = "catch";
            List<GraphNode> catchDownstream = new List<GraphNode>();
            foreach (var catchSyntax in node.Catches)
            {
                catchDownstream.AddRange(ConnectProgeny(catchSyntax, UpstreamNodes));
            }
            downstreamNodes.AddRange(catchDownstream);
            UpstreamNodes = downstreamNodes;
            if (node.Finally != null)
            {
                //finallyノードを作る場合
                UpstreamNodes = ConnectProgeny(node.Finally.Block, UpstreamNodes, "finally");
            }
        }

        public override void VisitLabeledStatement(LabeledStatementSyntax node)
        {
            var gNode = CreateGraphNoed(node);
            UpstreamNodes = ConnectUpstreamNodes(gNode, UpstreamNodes);
        }

        public override void VisitBreakStatement(BreakStatementSyntax node)
        {
            var gNode = CreateGraphNoed(node);
            UpstreamNodes = ConnectUpstreamNodes(gNode, UpstreamNodes, Param.link_none);
        }

        public override void VisitContinueStatement(ContinueStatementSyntax node)
        {
            var gNode = CreateGraphNoed(node);
            UpstreamNodes = ConnectUpstreamNodes(gNode, UpstreamNodes, Param.link_none);
        }

        public override void VisitExpressionStatement(ExpressionStatementSyntax node)
        {
            var gNode = CreateGraphNoed(node);
            UpstreamNodes = ConnectUpstreamNodes(gNode, UpstreamNodes);
            base.DefaultVisit(node);
        }

        public override void VisitReturnStatement(ReturnStatementSyntax node)
        {
            var gNode = CreateGraphNoed(node);
            UpstreamNodes = ConnectUpstreamNodes(gNode, UpstreamNodes, Param.link_none);
        }

        public override void VisitUsingStatement(UsingStatementSyntax node)
        {
            var gNode = CreateGraphNoed(node, Param.label_key, Param.label_shortStatement);
            UpstreamNodes = ConnectUpstreamNodes(gNode, UpstreamNodes);
            UpstreamNodes = ConnectProgeny(node.Statement, UpstreamNodes);
        }

        private enum Param
        {
            comment_wide,
            comment_medium,
            comment_nallow,
            label_key,
            label_statement,
            label_shortStatement, //{以降は無視
            label_noInnerBlock,
            label_kindAndStatement,
            label_none,
            edgeLabel_center,
            edgeLabel_tail,
            connect_ContinueStatement,
            connect_ReturnStatement,
            join_BreakStatement,
            direct_child,
            shape_rect,
            shape_ellipse,
            shape_diamond,
            shape_hexagon,
            shape_circle,
            shape_point,
            shape_Mdiamond,
            link_replace,
            link_pass,
            link_none
        }

        private IEnumerable<Param> GetParameters(params Param[] opts)
        {
            List<Param> paramList = new List<Param>(opts);
            paramList.AddRange(defaultOpts.Where(x => !paramList.Any(y => x.ToString().StartsWith(y.ToString().Substring(0, y.ToString().IndexOf("_") + 1)))).ToList());
            return paramList;
        }

        private Param[] defaultOpts = {
            Param.comment_nallow,
            Param.label_statement,
            Param.edgeLabel_center,
            Param.shape_rect,
            Param.link_replace
            };

        private GraphNode CreateGraphNoed(SyntaxNode node, params Param[] args)
        {
            return CreateGraphNoed(node, "", args);
        }

        private GraphNode CreateGraphNoed(SyntaxNode node, string label = "", params Param[] args)
        {
            var allOpts = GetParameters(args);
            string _label = label;
            if (_label == string.Empty)
            {
                if (allOpts.Contains(Param.label_none))
                {
                    _label = string.Empty;
                }
                else
                {
                    _label = DefaultNodeLabel(node, allOpts);
                }
            }
            string nodeShape = allOpts.First(x => x.ToString().StartsWith("shape_")).ToString().Replace("shape_", ""); ;
            return FlowGraph.CreateGraphNode(node, _label, nodeShape);
        }

        private List<GraphNode> ConnectUpstreamNodes(GraphNode gNode, List<GraphNode> tailNodes, params Param[] args)
        {
            var allOpts = GetParameters(args);
            var bandleNodes = new List<GraphNode>(tailNodes);
            //goto受付
            if (gNode.AstNode is LabeledStatementSyntax labelNode)
            {
                bandleNodes.AddRange(GotoNoeds(labelNode));
            }
            //return受付
            if (allOpts.Contains(Param.connect_ReturnStatement))
            {
                bandleNodes.AddRange(JumpInNodes(gNode.AstNode, "ReturnStatement"));
            }
            //continue受付
            if (allOpts.Contains(Param.connect_ContinueStatement))
            {
                bandleNodes.AddRange(JumpInNodes(gNode.AstNode, "ContinueStatement"));
            }
            //リンク作成
            foreach (GraphNode tailNode in bandleNodes)
            {
                FlowGraph.AddEdge(tailNode, gNode, tailNode.TransitEdgeLabel);
            }
            //下流へ渡すノード作成
            if (allOpts.Any(x => x == Param.direct_child))
            {
                bandleNodes = new SyntaxVisitor(FlowGraph,Scope).VisitSyntax(gNode.AstNode, tailNodes);
            }
            else
            {
                if (allOpts.Contains(Param.link_replace))
                {
                    bandleNodes.Clear();
                    bandleNodes.Add(gNode);
                }
                else if (allOpts.Contains(Param.link_none))
                {
                    bandleNodes.Clear();
                    Scope.KeywordNodes[gNode.AstNode.Kind().ToString()].Add(gNode);
                }
            }
            //break受付
            if (allOpts.Contains(Param.join_BreakStatement))
            {
                bandleNodes.AddRange(JumpInNodes(gNode.AstNode, "BreakStatement"));
            }

            return bandleNodes;
        }

        private List<GraphNode> ConnectProgeny(SyntaxNode node, List<GraphNode> tailNodes, string edgeLabel, params Param[] args)
        {
            var allOpts = GetParameters(args);
            var bandleNodes = new List<GraphNode>(tailNodes);
            //continue受付
            if (allOpts.Contains(Param.connect_ContinueStatement))
            {
                bandleNodes.AddRange(JumpInNodes(node, "ContinueStatement"));
            }
            bandleNodes = new SyntaxVisitor(FlowGraph,Scope).VisitSyntax(node, tailNodes, edgeLabel);
            return bandleNodes;
        }

        private List<GraphNode> ConnectProgeny(SyntaxNode node, List<GraphNode> tailNodes, params Param[] args)
        {
            return ConnectProgeny(node, tailNodes, string.Empty, args);
        }

        private IEnumerable<GraphNode> GotoNoeds(LabeledStatementSyntax labelNode)
        {
            var gotoNodes = new List<GraphNode>();
            var gotoList = Scope.KeywordNodes["GotoStatement"];
            for (int i = gotoList.Count - 1; i >= 0; i--)
            {
                var bNode = gotoList.ElementAt(i);
                if (IsAncesterHasSameMember(_root, bNode.AstNode))
                {
                    ExpressionSyntax expr = ((GotoStatementSyntax)bNode.AstNode).Expression;
                    if (((IdentifierNameSyntax)expr).Identifier.ToString() == labelNode.Identifier.ToString())
                    {
                        gotoNodes.Add(bNode);
                        gotoList.Remove(bNode);
                    }
                }
            }
            return gotoNodes;
        }

        private IEnumerable<GraphNode> JumpInNodes(SyntaxNode node, string key)
        {
            var jumpNodes = new List<GraphNode>();
            for (int i = Scope.KeywordNodes[key].Count - 1; i >= 0; i--)
            {
                var bNode = Scope.KeywordNodes[key].ElementAt(i);

                if (IsAncesterdNode(node, bNode.AstNode))
                {
                    jumpNodes.Add(bNode);
                    Scope.KeywordNodes[key].Remove(bNode);
                }
            }
            return jumpNodes;
        }

        private string DefaultNodeLabel(SyntaxNode node, IEnumerable<Param> opts)
        {
            string commnet = NodeLabelCommentPart(node, opts);
            return commnet + (commnet.Length > 0 ? "\\n" : "") + NodeLabeStatementPart(node, opts);
        }

        private string NodeLabeStatementPart(SyntaxNode node, IEnumerable<Param> allOpts)
        {
            var condTypes = new List<Type>() {
                typeof(IfStatementSyntax),
                typeof(ForStatementSyntax),
                typeof(WhileStatementSyntax),
                typeof(ForEachStatementSyntax),
                typeof(SwitchStatementSyntax),
                typeof(DoStatementSyntax),
                typeof(UsingStatementSyntax)
            };
            var keyStr = string.Empty;
            var nodesTokens = node.DescendantNodesAndTokens(_ => true).Where(_ => _.IsToken);
            var nodeToken = nodesTokens.ElementAt(0);
            if (allOpts.Contains(Param.label_key))
            {
                keyStr = nodeToken.ToString();
            }
            else if (allOpts.Contains(Param.label_kindAndStatement))
            {
                if (node is DoStatementSyntax doStatement)
                {
                    keyStr = doStatement.WhileKeyword.ToString();
                }
                else
                {
                    keyStr = nodeToken.ToString();
                }
            }
            string statementStr = string.Empty;
            if (allOpts.Contains(Param.label_kindAndStatement) || allOpts.Contains(Param.label_shortStatement) || allOpts.Contains(Param.label_statement) || allOpts.Contains(Param.label_noInnerBlock))
            {
                if (condTypes.Contains(node.GetType()))
                {
                    var parent = node.DescendantNodesAndTokens(_ => true).Where(_ => _.IsKind(SyntaxKind.OpenParenToken) || _.IsKind(SyntaxKind.CloseParenToken));
                    if (parent.Count() > 0)
                    {
                        int st = parent.First().SpanStart - node.SpanStart;
                        int en = parent.Last().SpanStart - parent.First().SpanStart + 1;
                        int bracketsCount = 0;
                        foreach (var p in parent)
                        {
                            if (p.IsKind(SyntaxKind.OpenParenToken))
                            {
                                bracketsCount++;
                            }
                            else if (p.IsKind(SyntaxKind.CloseParenToken))
                            {
                                bracketsCount--;
                                if (bracketsCount == 0)
                                {
                                    en = p.SpanStart - parent.First().SpanStart + 1;
                                    break;
                                }
                            }
                        }
                        statementStr = node.ToString().Substring(st, en);
                    }
                    else
                    {
                        statementStr = node.ToString();
                    }
                }
                else if (node is ConstructorDeclarationSyntax constructorDecl)
                {
                    statementStr = constructorDecl.Identifier.ToString();
                }
                else if (node is MethodDeclarationSyntax mesthodDecl)
                {
                    statementStr = mesthodDecl.Identifier.ToString();
                }
                else
                {
                    statementStr = node.ToString();
                }
                statementStr = Regex.Replace(statementStr, "[ |\t|;]*$", "");
                if (allOpts.Contains(Param.label_noInnerBlock))
                {
                    Regex re = new Regex("{.*}", RegexOptions.Singleline);
                    statementStr = re.Replace(statementStr, "{}");
                }
                if (allOpts.Contains(Param.label_shortStatement))
                {
                    statementStr = Regex.Replace(statementStr, "{.*$", "");
                }
            }
            return keyStr + statementStr;
        }

        private string NodeLabelCommentPart(SyntaxNode node, IEnumerable<Param> allOpts)
        {
            if (!allOpts.Any(_ => _.ToString().StartsWith("comment_")))
            {
                return string.Empty;
            }
            int endOfLine = 0;
            StringBuilder sb = new StringBuilder("");
            var nodesOrTokens = node.DescendantNodesAndTokens(_ => true).Where(_ => _.HasLeadingTrivia || _.IsToken);
            if (nodesOrTokens.Count() > 0)
            {
                var leadingTrvia = nodesOrTokens.First().GetLeadingTrivia();
                for (var i = leadingTrvia.Count - 1; i >= 0; i--)
                {
                    var tr = leadingTrvia[i];
                    if (tr.IsKind(SyntaxKind.EndOfLineTrivia))
                    {
                        if (allOpts.Contains(Param.comment_nallow) && endOfLine > 0)
                        {
                            break;
                        }
                        endOfLine++;
                    }
                    if ((allOpts.Contains(Param.comment_medium) || allOpts.Contains(Param.comment_nallow)) && tr.Kind() == SyntaxKind.SingleLineCommentTrivia)
                    {
                        string str = leadingTrvia[i].ToString();
                        str = Regex.Replace(str, "^//[( |\t|)]*", "");
                        sb.Insert(0, str + (sb.Length > 0 ? "\\n" : ""));
                        endOfLine = 0;
                    }
                    if ((allOpts.Contains(Param.comment_wide) && tr.Kind() == SyntaxKind.MultiLineCommentTrivia))
                    {
                        string[] strLines = leadingTrvia[i].ToString().Split('\n');
                        for (var j = strLines.Length - 1; j >= 0; j--)
                        {
                            string strl = strLines[j];
                            if (j == strLines.Length - 1)
                            {
                                strl = Regex.Replace(strl, "[( |\t|)]*\\*/$", "");
                            }
                            if (j == 0)
                            {
                                strl = Regex.Replace(strl, "^/\\*[( |\t|)]*", "");
                            }
                            if (j > 0)
                            {
                                strl = Regex.Replace(strl, "^[( |\t|)]*\\*[( |\t|)]*", "");
                            }
                            sb.Insert(0, strl + (sb.Length > 0 ? "\\n" : ""));
                        }
                        endOfLine = 0;
                    }
                }
            }
            return sb.ToString();
        }

        public class ScopeInfo<T>
        {
            public Dictionary<string, List<T>> KeywordNodes { get; } = new Dictionary<string, List<T>>();
            public ScopeInfo()
            {
                KeywordNodes["ReturnStatement"] = new List<T>();
                KeywordNodes["BreakStatement"] = new List<T>();
                KeywordNodes["GotoStatement"] = new List<T>();
                KeywordNodes["ContinueStatement"] = new List<T>();
            }
        }
    } 
}

(3)Dotファイル出力

FlowGraph.cs
using System.Linq;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Text;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using Microsoft.CodeAnalysis;

namespace Flow
{
    public class FlowGraph
    {
        private static int WORD_WRAP_LENGTH = 40;
        private List<GraphEdge> EdgeList = new List<GraphEdge>();
        private Dictionary<SyntaxNode, GraphNode> NodeMap = new System.Collections.Generic.Dictionary<SyntaxNode, GraphNode>();
        private int _seq = 0;

        public FlowGraph(string sourceCode)
        {
            var tree = CSharpSyntaxTree.ParseText(sourceCode);
            var memberDeclarations = tree.GetRoot().DescendantNodes().OfType<BaseMethodDeclarationSyntax>();

            foreach (var funcRoot in memberDeclarations)
            {
                SyntaxVisitor ast = new SyntaxVisitor(this);
                if (funcRoot is MethodDeclarationSyntax || funcRoot is ConstructorDeclarationSyntax)
                    ast.VisitRootSyntax(funcRoot);
            }
        }

        public GraphNode CreateGraphNode(SyntaxNode astNode, string labelName, string nodeShape)
        {
            GraphNode gNode = new GraphNode(astNode, "node" + (++_seq), labelName, nodeShape);
            NodeMap[astNode] = gNode;
            return gNode;
        }

        public GraphNode CreateGraphNode(string labelName, string nodeShape)
        {
            return new GraphNode("node" + (++_seq), labelName, nodeShape);
        }

        public GraphNode GetGraphNode(SyntaxNode node)
        {
            if (NodeMap.ContainsKey(node))
            {
                return NodeMap[node];
            }
            else
            {
                return null;
            }
        }

        public int GetNodeSequence()
        {
            return ++_seq;
        }

        public void AddEdge(GraphNode tail, GraphNode head)
        {
            AddEdge(tail, head, "");
        }

        public void AddEdge(GraphNode tail, GraphNode head, string centerLabel = "", string tailLabel = "", string headLabel = "")
        {
            EdgeList.Add(new GraphEdge(tail, head, centerLabel, tailLabel, headLabel));
        }

        public void WriteDot(string dotFilename)
        {
            using (System.IO.StreamWriter writer = new System.IO.StreamWriter(dotFilename, false))
            {
                writer.WriteLine("digraph G{");
                writer.WriteLine("rankdir=TB;");
                writer.WriteLine("node[fontname   =  \"MS GOTHIC\"]");
                writer.WriteLine("edge[fontname   =  \"MS GOTHIC\"]");
                foreach (GraphNode node in NodeMap.Values)
                {
                    string wrapStr = LabelWordWrap(node.LabelName, WORD_WRAP_LENGTH);
                    writer.WriteLine("\"" + node.NodeId + "\"" + "[shape = \"" + node.NodeShape + "\""
                              + ",  color = \"\"" + ", label = \"" + wrapStr.Replace("\"", "\\\"") + "\"]");
                }

                foreach (GraphEdge edge in EdgeList)
                {
                    writer.WriteLine("\"" + edge.TailNode.NodeId + "\"  -> \"" + edge.HeadNode.NodeId
                         + "\"[label =\"" + edge.CenterLabel + "\"  ,taillabel =\""
                         + edge.TailLabel + "\"  , color = \"" + "black" + "\"]");
                }
                writer.WriteLine("}");
            }
        }

        private static string LabelWordWrap(string text, int length)
        {
            Encoding sjis = Encoding.GetEncoding("Shift_JIS");
            var words = Regex.Split(text, @"( |\(|\)|=[=>]|\{|\}|\\n)");
            StringBuilder sb = new StringBuilder();
            int len = 0;
            foreach (var str in words)
            {
                int bytes = sjis.GetByteCount(str);
                if ((len + bytes) > length || bytes > length)
                {
                    sb.Append("\\n");
                    len = 0;
                }
                sb.Append(str);
                if (str == "\\n")
                {
                    len = 0;
                }
                len += bytes;
            }
            return sb.ToString();
        }
    }

    public class GraphNode
    {
        public SyntaxNode AstNode { get; set; }
        public string NodeId { get; set; }
        public string LabelName { get; set; }
        public string NodeShape { get; set; }
        public string TransitEdgeLabel { get; set; } = "";
        public GraphNode(SyntaxNode astNode, string id, string labelName, string nodeShape)
        {
            AstNode = astNode;
            NodeId = id;
            LabelName = labelName;
            NodeShape = nodeShape;
        }

        public GraphNode(string id, string labelName, string nodeShape) : this(null, id, labelName, nodeShape)
        {
        }
    }

    public class GraphEdge
    {
        public GraphNode TailNode { get; set; }
        public GraphNode HeadNode { get; set; }
        public string TailLabel { get; set; }
        public string CenterLabel { get; set; } = "";
        public string HeadLabel { get; set; } = "";
        public GraphEdge(GraphNode tailNode, GraphNode headNode, string centerLabel = "", string tailLabel = "", string headLabe = "")
        {
            TailNode = tailNode;
            HeadNode = headNode;
            TailLabel = tailLabel;
            CenterLabel = centerLabel;
            HeadLabel = headLabe;
        }
    }
}

17
22
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
22