public String getDOT(ATNState startState, String[] ruleNames, boolean isLexer) {
		if ( startState==null )	return null;

		// The output DOT graph for visualization
		Set<ATNState> markedStates = new HashSet<ATNState>();
		ST dot = stlib.getInstanceOf("atn");
		dot.add("startState", startState.stateNumber);
		dot.add("rankdir", rankdir);

		List<ATNState> work = new LinkedList<ATNState>();

		work.add(startState);
		while ( !work.isEmpty() ) {
			ATNState s = work.get(0);
			if ( markedStates.contains(s) ) { work.remove(0); continue; }
			markedStates.add(s);

			// don't go past end of rule node to the follow states
			if ( s instanceof 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
			ST edgeST;
			for (int i = 0; i < s.getNumberOfTransitions(); i++) {
				Transition edge = s.transition(i);
				if ( edge instanceof 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).isLeftRecursiveRule) {
						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.add(rr.followState);
					continue;
				}
				if ( edge instanceof ActionTransition) {
					edgeST = stlib.getInstanceOf("action-edge");
					edgeST.add("label", getEdgeLabel(edge.toString()));
				}
				else if ( edge instanceof 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()));
					boolean loopback = false;
					if (edge.target instanceof PlusBlockStartState) {
						loopback = s.equals(((PlusBlockStartState)edge.target).loopBackState);
					}
					else if (edge.target instanceof StarLoopEntryState) {
						loopback = s.equals(((StarLoopEntryState)edge.target).loopBackState);
					}
					edgeST.add("loopback", loopback);
				}
				else if ( edge instanceof AtomTransition ) {
					edgeST = stlib.getInstanceOf("edge");
					AtomTransition atom = (AtomTransition)edge;
					String label = String.valueOf(atom.label);
					if ( isLexer ) label = "'"+getEdgeLabel(new StringBuilder().appendCodePoint(atom.label).toString())+"'";
					else if ( grammar!=null ) label = grammar.getTokenDisplayName(atom.label);
					edgeST.add("label", getEdgeLabel(label));
				}
				else if ( edge instanceof 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 instanceof NotSetTransition ) label = "~"+label;
					edgeST.add("label", getEdgeLabel(label));
				}
				else if ( edge instanceof 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.getNumberOfTransitions() > 1) {
					edgeST.add("transitionIndex", i);
				}
				else {
					edgeST.add("transitionIndex", false);
				}
				dot.add("edges", edgeST);
				work.add(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);
//		}
		for (ATNState s : markedStates) {
			if ( !(s instanceof RuleStopState) ) continue;
			ST st = stlib.getInstanceOf("stopstate");
			st.add("name", "s"+s.stateNumber);
			st.add("label", getStateLabel(s));
			dot.add("states", st);
		}

		for (ATNState s : markedStates) {
			if ( s instanceof RuleStopState ) continue;
			ST st = stlib.getInstanceOf("state");
			st.add("name", "s"+s.stateNumber);
			st.add("label", getStateLabel(s));
			st.add("transitions", s.getTransitions());
			dot.add("states", st);
		}

		return dot.render();
	}
Return a String containing a DOT description that, when displayed,
 will show the incoming state machine visually.  All nodes reachable
 from startState will be included.
tool/src/org/antlr/v4/tool/DOTGenerator.java