public virtual string GetDOT(ATNState startState, string[] ruleNames, bool isLexer)
{
if (startState == null)
return null;
// The output DOT graph for visualization
ISet<ATNState> markedStates = new HashSet<ATNState>();
Template dot = stlib.GetInstanceOf("atn");
dot.Add("startState", startState.stateNumber);
dot.Add("rankdir", rankdir);
Queue<ATNState> work = new Queue<ATNState>();
work.Enqueue(startState);
while (work.Count > 0)
{
ATNState s = work.Peek();
if (markedStates.Contains(s))
{
work.Dequeue();
continue;
}
markedStates.Add(s);
// don't go past end of rule node to the follow states
if (s is RuleStopState)
continue;
// special case: if decision point, then line up the alt start states
// unless it's an end of block
// if ( s instanceof BlockStartState ) {
// ST rankST = stlib.getInstanceOf("decision-rank");
// DecisionState alt = (DecisionState)s;
// for (int i=0; i<alt.getNumberOfTransitions(); i++) {
// ATNState target = alt.transition(i).target;
// if ( target!=null ) {
// rankST.add("states", target.stateNumber);
// }
// }
// dot.add("decisionRanks", rankST);
// }
// make a DOT edge for each transition
Template edgeST;
for (int i = 0; i < s.NumberOfTransitions; i++)
{
Transition edge = s.Transition(i);
if (edge is RuleTransition)
{
RuleTransition rr = ((RuleTransition)edge);
// don't jump to other rules, but display edge to follow node
edgeST = stlib.GetInstanceOf("edge");
string label = "<" + ruleNames[rr.ruleIndex];
if (((RuleStartState)rr.target).isPrecedenceRule)
{
label += "[" + rr.precedence + "]";
}
label += ">";
edgeST.Add("label", label);
edgeST.Add("src", "s" + s.stateNumber);
edgeST.Add("target", "s" + rr.followState.stateNumber);
edgeST.Add("arrowhead", arrowhead);
dot.Add("edges", edgeST);
work.Enqueue(rr.followState);
continue;
}
if (edge is ActionTransition)
{
edgeST = stlib.GetInstanceOf("action-edge");
edgeST.Add("label", GetEdgeLabel(edge.ToString()));
}
else if (edge is AbstractPredicateTransition)
{
edgeST = stlib.GetInstanceOf("edge");
edgeST.Add("label", GetEdgeLabel(edge.ToString()));
}
else if (edge.IsEpsilon)
{
edgeST = stlib.GetInstanceOf("epsilon-edge");
edgeST.Add("label", GetEdgeLabel(edge.ToString()));
bool loopback = false;
if (edge.target is PlusBlockStartState)
{
loopback = s.Equals(((PlusBlockStartState)edge.target).loopBackState);
}
else if (edge.target is StarLoopEntryState)
{
loopback = s.Equals(((StarLoopEntryState)edge.target).loopBackState);
}
edgeST.Add("loopback", loopback);
}
else if (edge is AtomTransition)
{
edgeST = stlib.GetInstanceOf("edge");
AtomTransition atom = (AtomTransition)edge;
string label = atom.label.ToString();
if (isLexer)
label = "'" + GetEdgeLabel(((char)atom.label).ToString()) + "'";
else if (grammar != null)
label = grammar.GetTokenDisplayName(atom.label);
edgeST.Add("label", GetEdgeLabel(label));
}
else if (edge is SetTransition)
{
edgeST = stlib.GetInstanceOf("edge");
SetTransition set = (SetTransition)edge;
string label = set.Label.ToString();
if (isLexer)
label = set.Label.ToString(true);
else if (grammar != null)
label = set.Label.ToString(grammar.GetVocabulary());
if (edge is NotSetTransition)
label = "~" + label;
edgeST.Add("label", GetEdgeLabel(label));
}
else if (edge is RangeTransition)
{
edgeST = stlib.GetInstanceOf("edge");
RangeTransition range = (RangeTransition)edge;
string label = range.Label.ToString();
if (isLexer)
label = range.ToString();
else if (grammar != null)
label = range.Label.ToString(grammar.GetVocabulary());
edgeST.Add("label", GetEdgeLabel(label));
}
else
{
edgeST = stlib.GetInstanceOf("edge");
edgeST.Add("label", GetEdgeLabel(edge.ToString()));
}
edgeST.Add("src", "s" + s.stateNumber);
edgeST.Add("target", "s" + edge.target.stateNumber);
edgeST.Add("arrowhead", arrowhead);
if (s.NumberOfTransitions > 1)
{
edgeST.Add("transitionIndex", i);
}
else
{
edgeST.Add("transitionIndex", false);
}
dot.Add("edges", edgeST);
work.Enqueue(edge.target);
}
}
// define nodes we visited (they will appear first in DOT output)
// this is an example of ST's lazy eval :)
// define stop state first; seems to be a bug in DOT where doublecircle
// shape only works if we define them first. weird.
// ATNState stopState = startState.atn.ruleToStopState.get(startState.rule);
// if ( stopState!=null ) {
// ST st = stlib.getInstanceOf("stopstate");
// st.add("name", "s"+stopState.stateNumber);
// st.add("label", getStateLabel(stopState));
// dot.add("states", st);
// }
foreach (ATNState s in markedStates)
{
if (!(s is RuleStopState))
continue;
Template st = stlib.GetInstanceOf("stopstate");
st.Add("name", "s" + s.stateNumber);
st.Add("label", GetStateLabel(s));
dot.Add("states", st);
}
foreach (ATNState s in markedStates)
{
if (s is RuleStopState)
continue;
Template st = stlib.GetInstanceOf("state");
st.Add("name", "s" + s.stateNumber);
st.Add("label", GetStateLabel(s));
st.Add("transitions", s.Transitions);
dot.Add("states", st);
}
return dot.Render();
}