diff --git a/NRefactory/ICSharpCode.NRefactory.Tests/CSharp/Analysis/DefiniteAssignmentTests.cs b/NRefactory/ICSharpCode.NRefactory.Tests/CSharp/Analysis/DefiniteAssignmentTests.cs
new file mode 100644
index 000000000..247c1fb39
--- /dev/null
+++ b/NRefactory/ICSharpCode.NRefactory.Tests/CSharp/Analysis/DefiniteAssignmentTests.cs
@@ -0,0 +1,132 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under MIT X11 license (for details please see \doc\license.txt)
+
+using System;
+using System.Linq;
+using NUnit.Framework;
+
+namespace ICSharpCode.NRefactory.CSharp.Analysis
+{
+ [TestFixture]
+ public class DefiniteAssignmentTests
+ {
+ [Test]
+ public void TryFinally()
+ {
+ BlockStatement block = new BlockStatement {
+ new TryCatchStatement {
+ TryBlock = new BlockStatement {
+ new GotoStatement("LABEL"),
+ new AssignmentExpression(new IdentifierExpression("i"), new PrimitiveExpression(1))
+ },
+ CatchClauses = {
+ new CatchClause {
+ Body = new BlockStatement {
+ new AssignmentExpression(new IdentifierExpression("i"), new PrimitiveExpression(3))
+ }
+ }
+ },
+ FinallyBlock = new BlockStatement {
+ new AssignmentExpression(new IdentifierExpression("j"), new PrimitiveExpression(5))
+ }
+ },
+ new LabelStatement { Label = "LABEL" },
+ new EmptyStatement()
+ };
+ TryCatchStatement tryCatchStatement = (TryCatchStatement)block.Statements.First();
+ Statement stmt1 = tryCatchStatement.TryBlock.Statements.ElementAt(1);
+ Statement stmt3 = tryCatchStatement.CatchClauses.Single().Body.Statements.Single();
+ Statement stmt5 = tryCatchStatement.FinallyBlock.Statements.Single();
+ LabelStatement label = (LabelStatement)block.Statements.ElementAt(1);
+
+ DefiniteAssignmentAnalysis da = new DefiniteAssignmentAnalysis(block);
+ da.Analyze("i");
+ Assert.AreEqual(0, da.UnassignedVariableUses.Count);
+ Assert.AreEqual(DefiniteAssignmentStatus.PotentiallyAssigned, da.GetStatusBefore(tryCatchStatement));
+ Assert.AreEqual(DefiniteAssignmentStatus.CodeUnreachable, da.GetStatusBefore(stmt1));
+ Assert.AreEqual(DefiniteAssignmentStatus.CodeUnreachable, da.GetStatusAfter(stmt1));
+ Assert.AreEqual(DefiniteAssignmentStatus.PotentiallyAssigned, da.GetStatusBefore(stmt3));
+ Assert.AreEqual(DefiniteAssignmentStatus.DefinitelyAssigned, da.GetStatusAfter(stmt3));
+ Assert.AreEqual(DefiniteAssignmentStatus.PotentiallyAssigned, da.GetStatusBefore(stmt5));
+ Assert.AreEqual(DefiniteAssignmentStatus.PotentiallyAssigned, da.GetStatusAfter(stmt5));
+ Assert.AreEqual(DefiniteAssignmentStatus.DefinitelyAssigned, da.GetStatusAfter(tryCatchStatement));
+ Assert.AreEqual(DefiniteAssignmentStatus.DefinitelyAssigned, da.GetStatusBefore(label));
+ Assert.AreEqual(DefiniteAssignmentStatus.PotentiallyAssigned, da.GetStatusAfter(label));
+
+ da.Analyze("j");
+ Assert.AreEqual(0, da.UnassignedVariableUses.Count);
+ Assert.AreEqual(DefiniteAssignmentStatus.PotentiallyAssigned, da.GetStatusBefore(tryCatchStatement));
+ Assert.AreEqual(DefiniteAssignmentStatus.CodeUnreachable, da.GetStatusBefore(stmt1));
+ Assert.AreEqual(DefiniteAssignmentStatus.CodeUnreachable, da.GetStatusAfter(stmt1));
+ Assert.AreEqual(DefiniteAssignmentStatus.PotentiallyAssigned, da.GetStatusBefore(stmt3));
+ Assert.AreEqual(DefiniteAssignmentStatus.PotentiallyAssigned, da.GetStatusAfter(stmt3));
+ Assert.AreEqual(DefiniteAssignmentStatus.PotentiallyAssigned, da.GetStatusBefore(stmt5));
+ Assert.AreEqual(DefiniteAssignmentStatus.DefinitelyAssigned, da.GetStatusAfter(stmt5));
+ Assert.AreEqual(DefiniteAssignmentStatus.DefinitelyAssigned, da.GetStatusAfter(tryCatchStatement));
+ Assert.AreEqual(DefiniteAssignmentStatus.DefinitelyAssigned, da.GetStatusBefore(label));
+ Assert.AreEqual(DefiniteAssignmentStatus.DefinitelyAssigned, da.GetStatusAfter(label));
+ }
+
+ [Test]
+ public void ConditionalAnd()
+ {
+ IfElseStatement ifStmt = new IfElseStatement {
+ Condition = new BinaryOperatorExpression {
+ Left = new BinaryOperatorExpression(new IdentifierExpression("x"), BinaryOperatorType.GreaterThan, new PrimitiveExpression(0)),
+ Operator = BinaryOperatorType.ConditionalAnd,
+ Right = new BinaryOperatorExpression {
+ Left = new ParenthesizedExpression {
+ Expression = new AssignmentExpression {
+ Left = new IdentifierExpression("i"),
+ Operator = AssignmentOperatorType.Assign,
+ Right = new IdentifierExpression("y")
+ }
+ },
+ Operator = BinaryOperatorType.GreaterThanOrEqual,
+ Right = new PrimitiveExpression(0)
+ }
+ },
+ TrueStatement = new BlockStatement(),
+ FalseStatement = new BlockStatement()
+ };
+ DefiniteAssignmentAnalysis da = new DefiniteAssignmentAnalysis(ifStmt);
+ da.Analyze("i");
+ Assert.AreEqual(0, da.UnassignedVariableUses.Count);
+ Assert.AreEqual(DefiniteAssignmentStatus.PotentiallyAssigned, da.GetStatusBefore(ifStmt));
+ Assert.AreEqual(DefiniteAssignmentStatus.DefinitelyAssigned, da.GetStatusBefore(ifStmt.TrueStatement));
+ Assert.AreEqual(DefiniteAssignmentStatus.PotentiallyAssigned, da.GetStatusBefore(ifStmt.FalseStatement));
+ Assert.AreEqual(DefiniteAssignmentStatus.PotentiallyAssigned, da.GetStatusAfter(ifStmt));
+ }
+
+ [Test]
+ public void ConditionalOr()
+ {
+ IfElseStatement ifStmt = new IfElseStatement {
+ Condition = new BinaryOperatorExpression {
+ Left = new BinaryOperatorExpression(new IdentifierExpression("x"), BinaryOperatorType.GreaterThan, new PrimitiveExpression(0)),
+ Operator = BinaryOperatorType.ConditionalOr,
+ Right = new BinaryOperatorExpression {
+ Left = new ParenthesizedExpression {
+ Expression = new AssignmentExpression {
+ Left = new IdentifierExpression("i"),
+ Operator = AssignmentOperatorType.Assign,
+ Right = new IdentifierExpression("y")
+ }
+ },
+ Operator = BinaryOperatorType.GreaterThanOrEqual,
+ Right = new PrimitiveExpression(0)
+ }
+ },
+ TrueStatement = new BlockStatement(),
+ FalseStatement = new BlockStatement()
+ };
+ DefiniteAssignmentAnalysis da = new DefiniteAssignmentAnalysis(ifStmt);
+ da.Analyze("i");
+ Assert.AreEqual(0, da.UnassignedVariableUses.Count);
+ Assert.AreEqual(DefiniteAssignmentStatus.PotentiallyAssigned, da.GetStatusBefore(ifStmt));
+ Assert.AreEqual(DefiniteAssignmentStatus.PotentiallyAssigned, da.GetStatusBefore(ifStmt.TrueStatement));
+ Assert.AreEqual(DefiniteAssignmentStatus.DefinitelyAssigned, da.GetStatusBefore(ifStmt.FalseStatement));
+ Assert.AreEqual(DefiniteAssignmentStatus.PotentiallyAssigned, da.GetStatusAfter(ifStmt));
+ }
+ }
+}
diff --git a/NRefactory/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj b/NRefactory/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj
index 7bc0981b5..6d351a08a 100644
--- a/NRefactory/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj
+++ b/NRefactory/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj
@@ -59,6 +59,7 @@
+
@@ -162,6 +163,7 @@
+
diff --git a/NRefactory/ICSharpCode.NRefactory/CSharp/Analysis/ControlFlow.cs b/NRefactory/ICSharpCode.NRefactory/CSharp/Analysis/ControlFlow.cs
index 3d0d25523..2f1e2b47e 100644
--- a/NRefactory/ICSharpCode.NRefactory/CSharp/Analysis/ControlFlow.cs
+++ b/NRefactory/ICSharpCode.NRefactory/CSharp/Analysis/ControlFlow.cs
@@ -79,6 +79,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis
{
if (jumpOutOfTryFinally == null)
jumpOutOfTryFinally = new List();
+ jumpOutOfTryFinally.Add(tryFinally);
}
///
diff --git a/NRefactory/ICSharpCode.NRefactory/CSharp/Analysis/DefiniteAssignmentAnalysis.cs b/NRefactory/ICSharpCode.NRefactory/CSharp/Analysis/DefiniteAssignmentAnalysis.cs
index 958e1642a..534e42325 100644
--- a/NRefactory/ICSharpCode.NRefactory/CSharp/Analysis/DefiniteAssignmentAnalysis.cs
+++ b/NRefactory/ICSharpCode.NRefactory/CSharp/Analysis/DefiniteAssignmentAnalysis.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using ICSharpCode.NRefactory.CSharp.Resolver;
+using ICSharpCode.NRefactory.Utils;
namespace ICSharpCode.NRefactory.CSharp.Analysis
{
@@ -19,10 +20,6 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis
///
PotentiallyAssigned,
///
- /// The variable is definitely unassigned.
- ///
- DefinitelyUnassigned,
- ///
/// The variable is definitely assigned.
///
DefinitelyAssigned,
@@ -82,7 +79,16 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis
}
}
- public void Analyze(string variable, DefiniteAssignmentStatus initialStatus = DefiniteAssignmentStatus.DefinitelyUnassigned)
+ ///
+ /// Gets the unassigned usages of the previously analyzed variable.
+ ///
+ public IList UnassignedVariableUses {
+ get {
+ return unassignedVariableUses.AsReadOnly();
+ }
+ }
+
+ public void Analyze(string variable, DefiniteAssignmentStatus initialStatus = DefiniteAssignmentStatus.PotentiallyAssigned)
{
this.variableName = variable;
// Reset the status:
@@ -93,7 +99,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis
edgeStatus[edge] = DefiniteAssignmentStatus.CodeUnreachable;
}
- ChangeNodeStatus(allNodes[0], DefiniteAssignmentStatus.DefinitelyUnassigned);
+ ChangeNodeStatus(allNodes[0], initialStatus);
// Iterate as long as the input status of some nodes is changing:
while (nodesWithModifiedInput.Count > 0) {
ControlFlowNode node = nodesWithModifiedInput.Dequeue();
@@ -105,6 +111,64 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis
}
}
+ public DefiniteAssignmentStatus GetStatusBefore(Statement statement)
+ {
+ return nodeStatus[beginNodeDict[statement]];
+ }
+
+ public DefiniteAssignmentStatus GetStatusAfter(Statement statement)
+ {
+ return nodeStatus[endNodeDict[statement]];
+ }
+
+ ///
+ /// Exports the CFG. This method is intended to help debugging issues related to definite assignment.
+ ///
+ public GraphVizGraph ExportGraph()
+ {
+ GraphVizGraph g = new GraphVizGraph();
+ g.Title = "DefiniteAssignment - " + variableName;
+ for (int i = 0; i < allNodes.Count; i++) {
+ string name = nodeStatus[allNodes[i]].ToString() + Environment.NewLine;
+ switch (allNodes[i].Type) {
+ case ControlFlowNodeType.StartNode:
+ case ControlFlowNodeType.BetweenStatements:
+ name += allNodes[i].NextStatement.ToString();
+ break;
+ case ControlFlowNodeType.EndNode:
+ name += "End of " + allNodes[i].PreviousStatement.ToString();
+ break;
+ case ControlFlowNodeType.LoopCondition:
+ name += "Condition in " + allNodes[i].NextStatement.ToString();
+ break;
+ default:
+ name += allNodes[i].Type.ToString();
+ break;
+ }
+ g.AddNode(new GraphVizNode(i) { label = name });
+ foreach (ControlFlowEdge edge in allNodes[i].Outgoing) {
+ GraphVizEdge ge = new GraphVizEdge(i, allNodes.IndexOf(edge.To));
+ if (edgeStatus.Count > 0)
+ ge.label = edgeStatus[edge].ToString();
+ if (edge.IsLeavingTryFinally)
+ ge.style = "dashed";
+ switch (edge.Type) {
+ case ControlFlowEdgeType.ConditionTrue:
+ ge.color = "green";
+ break;
+ case ControlFlowEdgeType.ConditionFalse:
+ ge.color = "red";
+ break;
+ case ControlFlowEdgeType.Jump:
+ ge.color = "blue";
+ break;
+ }
+ g.AddEdge(ge);
+ }
+ }
+ return g;
+ }
+
static DefiniteAssignmentStatus MergeStatus(DefiniteAssignmentStatus a, DefiniteAssignmentStatus b)
{
// The result will be DefinitelyAssigned if at least one incoming edge is DefinitelyAssigned and all others are unreachable.
@@ -151,9 +215,11 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis
TryCatchStatement tryFinally = (TryCatchStatement)node.PreviousStatement.Parent;
// Changing the status on a finally block potentially changes the status of all edges leaving that finally block:
foreach (ControlFlowEdge edge in allNodes.SelectMany(n => n.Outgoing)) {
- DefiniteAssignmentStatus s = edgeStatus[edge];
- if (s == DefiniteAssignmentStatus.DefinitelyUnassigned || s == DefiniteAssignmentStatus.PotentiallyAssigned) {
- ChangeEdgeStatus(edge, outputStatus);
+ if (edge.IsLeavingTryFinally && edge.TryFinallyStatements.Contains(tryFinally)) {
+ DefiniteAssignmentStatus s = edgeStatus[edge];
+ if (s == DefiniteAssignmentStatus.PotentiallyAssigned) {
+ ChangeEdgeStatus(edge, outputStatus);
+ }
}
}
}
@@ -197,19 +263,18 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis
if (oldStatus == newStatus)
return;
// Ensure that status can change only in one direction:
- // CodeUnreachable -> PotentiallyAssigned -> Definitely[Un]Assigned
+ // CodeUnreachable -> PotentiallyAssigned -> DefinitelyAssigned
// Going against this direction indicates a bug and could cause infinite loops.
- switch (newStatus) {
+ switch (oldStatus) {
case DefiniteAssignmentStatus.PotentiallyAssigned:
- if (oldStatus != DefiniteAssignmentStatus.CodeUnreachable)
+ if (newStatus != DefiniteAssignmentStatus.DefinitelyAssigned)
throw new InvalidOperationException("Invalid state transition");
break;
- case DefiniteAssignmentStatus.DefinitelyUnassigned:
- case DefiniteAssignmentStatus.DefinitelyAssigned:
- if (!(oldStatus == DefiniteAssignmentStatus.CodeUnreachable || oldStatus == DefiniteAssignmentStatus.PotentiallyAssigned))
+ case DefiniteAssignmentStatus.CodeUnreachable:
+ if (!(newStatus == DefiniteAssignmentStatus.PotentiallyAssigned || newStatus == DefiniteAssignmentStatus.DefinitelyAssigned))
throw new InvalidOperationException("Invalid state transition");
break;
- case DefiniteAssignmentStatus.CodeUnreachable:
+ case DefiniteAssignmentStatus.DefinitelyAssigned:
throw new InvalidOperationException("Invalid state transition");
default:
throw new InvalidOperationException("Invalid value for DefiniteAssignmentStatus");
@@ -449,8 +514,6 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis
return DefiniteAssignmentStatus.AssignedAfterTrueExpression;
else if (afterLeft == DefiniteAssignmentStatus.AssignedAfterFalseExpression && afterRight == DefiniteAssignmentStatus.AssignedAfterFalseExpression)
return DefiniteAssignmentStatus.AssignedAfterFalseExpression;
- else if (afterRight == DefiniteAssignmentStatus.DefinitelyUnassigned)
- return afterRight;
else
return DefiniteAssignmentStatus.PotentiallyAssigned;
} else if (binaryOperatorExpression.Operator == BinaryOperatorType.ConditionalOr) {
@@ -477,8 +540,6 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis
return DefiniteAssignmentStatus.AssignedAfterFalseExpression;
else if (afterLeft == DefiniteAssignmentStatus.AssignedAfterTrueExpression && afterRight == DefiniteAssignmentStatus.AssignedAfterTrueExpression)
return DefiniteAssignmentStatus.AssignedAfterTrueExpression;
- else if (afterRight == DefiniteAssignmentStatus.DefinitelyUnassigned)
- return DefiniteAssignmentStatus.DefinitelyUnassigned;
else
return DefiniteAssignmentStatus.PotentiallyAssigned;
} else if (binaryOperatorExpression.Operator == BinaryOperatorType.NullCoalescing) {
diff --git a/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/DepthFirstAstVisitor.cs b/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/DepthFirstAstVisitor.cs
index e0e9615f3..dff52990d 100644
--- a/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/DepthFirstAstVisitor.cs
+++ b/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/DepthFirstAstVisitor.cs
@@ -1,4 +1,4 @@
-//
+//
// IAstVisitor.cs
//
// Author:
@@ -52,17 +52,17 @@ namespace ICSharpCode.NRefactory.CSharp
public virtual S VisitComment (Comment comment, T data)
{
- return default (S);
+ return VisitChildren (comment, data);
}
public virtual S VisitIdentifier (Identifier identifier, T data)
{
- return default (S);
+ return VisitChildren (identifier, data);
}
public virtual S VisitCSharpTokenNode (CSharpTokenNode token, T data)
{
- return default (S);
+ return VisitChildren (token, data);
}
public virtual S VisitPrimitiveType (PrimitiveType primitiveType, T data)
diff --git a/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/Expressions/Expression.cs b/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/Expressions/Expression.cs
index 3055ae7fb..ea5e22f64 100644
--- a/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/Expressions/Expression.cs
+++ b/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/Expressions/Expression.cs
@@ -54,6 +54,8 @@ namespace ICSharpCode.NRefactory.CSharp
// Make debugging easier by giving Expressions a ToString() implementation
public override string ToString()
{
+ if (IsNull)
+ return "Null";
StringWriter w = new StringWriter();
AcceptVisitor(new OutputVisitor(w, new CSharpFormattingPolicy()), null);
return w.ToString();
diff --git a/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/Statements/Statement.cs b/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/Statements/Statement.cs
index 0d678ff59..ef01d54b3 100644
--- a/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/Statements/Statement.cs
+++ b/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/Statements/Statement.cs
@@ -2,6 +2,7 @@
// This code is distributed under MIT X11 license (for details please see \doc\license.txt)
using System;
+using System.IO;
namespace ICSharpCode.NRefactory.CSharp
{
@@ -52,5 +53,19 @@ namespace ICSharpCode.NRefactory.CSharp
public override NodeType NodeType {
get { return NodeType.Statement; }
}
+
+ // Make debugging easier by giving Statements a ToString() implementation
+ public override string ToString()
+ {
+ if (IsNull)
+ return "Null";
+ StringWriter w = new StringWriter();
+ AcceptVisitor(new OutputVisitor(w, new CSharpFormattingPolicy()), null);
+ string text = w.ToString().TrimEnd().Replace("\t", "").Replace(w.NewLine, " ");
+ if (text.Length > 100)
+ return text.Substring(0, 97) + "...";
+ else
+ return text;
+ }
}
}
diff --git a/NRefactory/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj b/NRefactory/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj
index 40d8f5afd..6a57c5315 100644
--- a/NRefactory/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj
+++ b/NRefactory/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj
@@ -334,6 +334,7 @@
+
diff --git a/NRefactory/ICSharpCode.NRefactory/Utils/GraphVizGraph.cs b/NRefactory/ICSharpCode.NRefactory/Utils/GraphVizGraph.cs
new file mode 100644
index 000000000..2bf93c353
--- /dev/null
+++ b/NRefactory/ICSharpCode.NRefactory/Utils/GraphVizGraph.cs
@@ -0,0 +1,204 @@
+// Copyright (c) 2010 AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under MIT X11 license (for details please see \doc\license.txt)
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+using System.IO;
+using System.Text.RegularExpressions;
+
+namespace ICSharpCode.NRefactory.Utils
+{
+ ///
+ /// GraphViz graph.
+ ///
+ public sealed class GraphVizGraph
+ {
+ List nodes = new List();
+ List edges = new List();
+
+ public string rankdir;
+ public string Title;
+
+ public void AddEdge(GraphVizEdge edge)
+ {
+ edges.Add(edge);
+ }
+
+ public void AddNode(GraphVizNode node)
+ {
+ nodes.Add(node);
+ }
+
+ public void Save(string fileName)
+ {
+ using (StreamWriter writer = new StreamWriter(fileName))
+ Save(writer);
+ }
+
+ public void Show()
+ {
+ Show(null);
+ }
+
+ public void Show(string name)
+ {
+ if (name == null)
+ name = Title;
+ if (name != null)
+ foreach (char c in Path.GetInvalidFileNameChars())
+ name = name.Replace(c, '-');
+ string fileName = name != null ? Path.Combine(Path.GetTempPath(), name) : Path.GetTempFileName();
+ Save(fileName + ".gv");
+ Process.Start("dot", "\"" + fileName + ".gv\" -Tpng -o \"" + fileName + ".png\"").WaitForExit();
+ Process.Start(fileName + ".png");
+ }
+
+ static string Escape(string text)
+ {
+ if (Regex.IsMatch(text, @"^[\w\d]+$")) {
+ return text;
+ } else {
+ return "\"" + text.Replace("\\", "\\\\").Replace("\r", "").Replace("\n", "\\n").Replace("\"", "\\\"") + "\"";
+ }
+ }
+
+ static void WriteGraphAttribute(TextWriter writer, string name, string value)
+ {
+ if (value != null)
+ writer.WriteLine("{0}={1};", name, Escape(value));
+ }
+
+ internal static void WriteAttribute(TextWriter writer, string name, double? value, ref bool isFirst)
+ {
+ if (value != null) {
+ WriteAttribute(writer, name, value.Value.ToString(CultureInfo.InvariantCulture), ref isFirst);
+ }
+ }
+
+ internal static void WriteAttribute(TextWriter writer, string name, bool? value, ref bool isFirst)
+ {
+ if (value != null) {
+ WriteAttribute(writer, name, value.Value ? "true" : "false", ref isFirst);
+ }
+ }
+
+ internal static void WriteAttribute(TextWriter writer, string name, string value, ref bool isFirst)
+ {
+ if (value != null) {
+ if (isFirst)
+ isFirst = false;
+ else
+ writer.Write(',');
+ writer.Write("{0}={1}", name, Escape(value));
+ }
+ }
+
+ public void Save(TextWriter writer)
+ {
+ if (writer == null)
+ throw new ArgumentNullException("writer");
+ writer.WriteLine("digraph G {");
+ writer.WriteLine("node [fontsize = 16];");
+ WriteGraphAttribute(writer, "rankdir", rankdir);
+ foreach (GraphVizNode node in nodes) {
+ node.Save(writer);
+ }
+ foreach (GraphVizEdge edge in edges) {
+ edge.Save(writer);
+ }
+ writer.WriteLine("}");
+ }
+ }
+
+ public sealed class GraphVizEdge
+ {
+ public readonly string Source, Target;
+
+ /// edge stroke color
+ public string color;
+ /// use edge to affect node ranking
+ public bool? constraint;
+
+ public string label;
+
+ public string style;
+
+ /// point size of label
+ public int? fontsize;
+
+ public GraphVizEdge(string source, string target)
+ {
+ if (source == null)
+ throw new ArgumentNullException("source");
+ if (target == null)
+ throw new ArgumentNullException("target");
+ this.Source = source;
+ this.Target = target;
+ }
+
+ public GraphVizEdge(int source, int target)
+ {
+ this.Source = source.ToString(CultureInfo.InvariantCulture);
+ this.Target = target.ToString(CultureInfo.InvariantCulture);
+ }
+
+ public void Save(TextWriter writer)
+ {
+ writer.Write("{0} -> {1} [", Source, Target);
+ bool isFirst = true;
+ GraphVizGraph.WriteAttribute(writer, "label", label, ref isFirst);
+ GraphVizGraph.WriteAttribute(writer, "style", style, ref isFirst);
+ GraphVizGraph.WriteAttribute(writer, "fontsize", fontsize, ref isFirst);
+ GraphVizGraph.WriteAttribute(writer, "color", color, ref isFirst);
+ GraphVizGraph.WriteAttribute(writer, "constraint", constraint, ref isFirst);
+ writer.WriteLine("];");
+ }
+ }
+
+ public sealed class GraphVizNode
+ {
+ public readonly string ID;
+ public string label;
+
+ public string labelloc;
+
+ /// point size of label
+ public int? fontsize;
+
+ /// minimum height in inches
+ public double? height;
+
+ /// space around label
+ public string margin;
+
+ /// node shape
+ public string shape;
+
+ public GraphVizNode(string id)
+ {
+ if (id == null)
+ throw new ArgumentNullException("id");
+ this.ID = id;
+ }
+
+ public GraphVizNode(int id)
+ {
+ this.ID = id.ToString(CultureInfo.InvariantCulture);
+ }
+
+ public void Save(TextWriter writer)
+ {
+ writer.Write(ID);
+ writer.Write(" [");
+ bool isFirst = true;
+ GraphVizGraph.WriteAttribute(writer, "label", label, ref isFirst);
+ GraphVizGraph.WriteAttribute(writer, "labelloc", labelloc, ref isFirst);
+ GraphVizGraph.WriteAttribute(writer, "fontsize", fontsize, ref isFirst);
+ GraphVizGraph.WriteAttribute(writer, "margin", margin, ref isFirst);
+ GraphVizGraph.WriteAttribute(writer, "shape", shape, ref isFirst);
+ writer.WriteLine("];");
+ }
+ }
+}