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("];"); + } + } +}