Browse Source

Fix issues in definite assignment analysis.

pull/100/head
Daniel Grunwald 15 years ago
parent
commit
c9ec992688
  1. 132
      NRefactory/ICSharpCode.NRefactory.Tests/CSharp/Analysis/DefiniteAssignmentTests.cs
  2. 2
      NRefactory/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj
  3. 1
      NRefactory/ICSharpCode.NRefactory/CSharp/Analysis/ControlFlow.cs
  4. 101
      NRefactory/ICSharpCode.NRefactory/CSharp/Analysis/DefiniteAssignmentAnalysis.cs
  5. 8
      NRefactory/ICSharpCode.NRefactory/CSharp/Ast/DepthFirstAstVisitor.cs
  6. 2
      NRefactory/ICSharpCode.NRefactory/CSharp/Ast/Expressions/Expression.cs
  7. 15
      NRefactory/ICSharpCode.NRefactory/CSharp/Ast/Statements/Statement.cs
  8. 1
      NRefactory/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj
  9. 204
      NRefactory/ICSharpCode.NRefactory/Utils/GraphVizGraph.cs

132
NRefactory/ICSharpCode.NRefactory.Tests/CSharp/Analysis/DefiniteAssignmentTests.cs

@ -0,0 +1,132 @@ @@ -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));
}
}
}

2
NRefactory/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj

@ -59,6 +59,7 @@ @@ -59,6 +59,7 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="CSharp\Analysis\DefiniteAssignmentTests.cs" />
<Compile Include="CSharp\AstStructureTests.cs" />
<Compile Include="CSharp\InsertParenthesesVisitorTests.cs" />
<Compile Include="CSharp\Parser\GeneralScope\DelegateDeclarationTests.cs" />
@ -162,6 +163,7 @@ @@ -162,6 +163,7 @@
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Folder Include="CSharp\Analysis" />
<Folder Include="CSharp\Parser\Expression" />
<Folder Include="CSharp\Parser\GeneralScope" />
<Folder Include="CSharp\Parser\TypeMembers" />

1
NRefactory/ICSharpCode.NRefactory/CSharp/Analysis/ControlFlow.cs

@ -79,6 +79,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis @@ -79,6 +79,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis
{
if (jumpOutOfTryFinally == null)
jumpOutOfTryFinally = new List<TryCatchStatement>();
jumpOutOfTryFinally.Add(tryFinally);
}
/// <summary>

101
NRefactory/ICSharpCode.NRefactory/CSharp/Analysis/DefiniteAssignmentAnalysis.cs

@ -6,6 +6,7 @@ using System.Collections.Generic; @@ -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 @@ -19,10 +20,6 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis
/// </summary>
PotentiallyAssigned,
/// <summary>
/// The variable is definitely unassigned.
/// </summary>
DefinitelyUnassigned,
/// <summary>
/// The variable is definitely assigned.
/// </summary>
DefinitelyAssigned,
@ -82,7 +79,16 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis @@ -82,7 +79,16 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis
}
}
public void Analyze(string variable, DefiniteAssignmentStatus initialStatus = DefiniteAssignmentStatus.DefinitelyUnassigned)
/// <summary>
/// Gets the unassigned usages of the previously analyzed variable.
/// </summary>
public IList<IdentifierExpression> 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 @@ -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 @@ -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]];
}
/// <summary>
/// Exports the CFG. This method is intended to help debugging issues related to definite assignment.
/// </summary>
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 @@ -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 @@ -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 @@ -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 @@ -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) {

8
NRefactory/ICSharpCode.NRefactory/CSharp/Ast/DepthFirstAstVisitor.cs

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
//
//
// IAstVisitor.cs
//
// Author:
@ -52,17 +52,17 @@ namespace ICSharpCode.NRefactory.CSharp @@ -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)

2
NRefactory/ICSharpCode.NRefactory/CSharp/Ast/Expressions/Expression.cs

@ -54,6 +54,8 @@ namespace ICSharpCode.NRefactory.CSharp @@ -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();

15
NRefactory/ICSharpCode.NRefactory/CSharp/Ast/Statements/Statement.cs

@ -2,6 +2,7 @@ @@ -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 @@ -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;
}
}
}

1
NRefactory/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj

@ -334,6 +334,7 @@ @@ -334,6 +334,7 @@
<Compile Include="Utils\DotNet35Compat.cs" />
<Compile Include="Utils\EmptyList.cs" />
<Compile Include="Utils\ExtensionMethods.cs" />
<Compile Include="Utils\GraphVizGraph.cs" />
<Compile Include="Utils\TreeTraversal.cs" />
<Compile Include="CSharp\Ast\ComposedType.cs" />
<Compile Include="CSharp\Ast\Expressions\DirectionExpression.cs" />

204
NRefactory/ICSharpCode.NRefactory/Utils/GraphVizGraph.cs

@ -0,0 +1,204 @@ @@ -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
{
/// <summary>
/// GraphViz graph.
/// </summary>
public sealed class GraphVizGraph
{
List<GraphVizNode> nodes = new List<GraphVizNode>();
List<GraphVizEdge> edges = new List<GraphVizEdge>();
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;
/// <summary>edge stroke color</summary>
public string color;
/// <summary>use edge to affect node ranking</summary>
public bool? constraint;
public string label;
public string style;
/// <summary>point size of label</summary>
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;
/// <summary>point size of label</summary>
public int? fontsize;
/// <summary>minimum height in inches</summary>
public double? height;
/// <summary>space around label</summary>
public string margin;
/// <summary>node shape</summary>
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("];");
}
}
}
Loading…
Cancel
Save