org.antlr.v4.tool.DOTGenerator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of antlr4 Show documentation
Show all versions of antlr4 Show documentation
The ANTLR 4 grammar compiler.
/*
* [The "BSD license"]
* Copyright (c) 2012 Terence Parr
* Copyright (c) 2012 Sam Harwell
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.antlr.v4.tool;
import org.antlr.v4.misc.Utils;
import org.antlr.v4.runtime.atn.ATNConfig;
import org.antlr.v4.runtime.atn.ATNState;
import org.antlr.v4.runtime.atn.AbstractPredicateTransition;
import org.antlr.v4.runtime.atn.ActionTransition;
import org.antlr.v4.runtime.atn.AtomTransition;
import org.antlr.v4.runtime.atn.BlockEndState;
import org.antlr.v4.runtime.atn.BlockStartState;
import org.antlr.v4.runtime.atn.DecisionState;
import org.antlr.v4.runtime.atn.NotSetTransition;
import org.antlr.v4.runtime.atn.PlusBlockStartState;
import org.antlr.v4.runtime.atn.PlusLoopbackState;
import org.antlr.v4.runtime.atn.RangeTransition;
import org.antlr.v4.runtime.atn.RuleStopState;
import org.antlr.v4.runtime.atn.RuleTransition;
import org.antlr.v4.runtime.atn.SetTransition;
import org.antlr.v4.runtime.atn.StarBlockStartState;
import org.antlr.v4.runtime.atn.StarLoopEntryState;
import org.antlr.v4.runtime.atn.StarLoopbackState;
import org.antlr.v4.runtime.atn.Transition;
import org.antlr.v4.runtime.dfa.DFA;
import org.antlr.v4.runtime.dfa.DFAState;
import org.stringtemplate.v4.ST;
import org.stringtemplate.v4.STGroup;
import org.stringtemplate.v4.STGroupFile;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/** The DOT (part of graphviz) generation aspect. */
public class DOTGenerator {
public static final boolean STRIP_NONREDUCED_STATES = false;
protected String arrowhead="normal";
protected String rankdir="LR";
/** Library of output templates; use {@code } format. */
public static STGroup stlib = new STGroupFile("org/antlr/v4/tool/templates/dot/graphs.stg");
protected Grammar grammar;
/** This aspect is associated with a grammar */
public DOTGenerator(Grammar grammar) {
this.grammar = grammar;
}
public String getDOT(DFA dfa, boolean isLexer) {
if ( dfa.s0.get()==null ) return null;
ST dot = stlib.getInstanceOf("dfa");
dot.add("name", "DFA"+dfa.decision);
dot.add("startState", dfa.s0.get().stateNumber);
// dot.add("useBox", Tool.internalOption_ShowATNConfigsInDFA);
dot.add("rankdir", rankdir);
// define stop states first; seems to be a bug in DOT where doublecircle
for (DFAState d : dfa.states.keySet()) {
if ( !d.isAcceptState ) continue;
ST st = stlib.getInstanceOf("stopstate");
st.add("name", "s"+d.stateNumber);
st.add("label", getStateLabel(d));
dot.add("states", st);
}
for (DFAState d : dfa.states.keySet()) {
if ( d.isAcceptState ) continue;
if ( d.stateNumber == Integer.MAX_VALUE ) continue;
ST st = stlib.getInstanceOf("state");
st.add("name", "s"+d.stateNumber);
st.add("label", getStateLabel(d));
dot.add("states", st);
}
for (DFAState d : dfa.states.keySet()) {
Map edges = d.getEdgeMap();
for (Map.Entry entry : edges.entrySet()) {
DFAState target = entry.getValue();
if ( target==null) continue;
if ( target.stateNumber == Integer.MAX_VALUE ) continue;
int ttype = entry.getKey();
String label = String.valueOf(ttype);
if ( isLexer ) label = "'"+getEdgeLabel(String.valueOf((char)ttype))+"'";
else if ( grammar!=null ) label = grammar.getTokenDisplayName(ttype);
ST st = stlib.getInstanceOf("edge");
st.add("label", label);
st.add("src", "s"+d.stateNumber);
st.add("target", "s"+target.stateNumber);
st.add("arrowhead", arrowhead);
dot.add("edges", st);
}
}
String output = dot.render();
return Utils.sortLinesInString(output);
}
protected String getStateLabel(DFAState s) {
if ( s==null ) return "null";
StringBuilder buf = new StringBuilder(250);
buf.append('s');
buf.append(s.stateNumber);
if ( s.isAcceptState ) {
buf.append("=>").append(s.prediction);
}
if ( grammar!=null ) {
BitSet alts = s.configs.getRepresentedAlternatives();
buf.append("\\n");
Set configurations = s.configs;
for (int alt = alts.nextSetBit(0); alt >= 0; alt = alts.nextSetBit(alt + 1)) {
if ( alt>alts.nextSetBit(0) ) {
buf.append("\\n");
}
buf.append("alt");
buf.append(alt);
buf.append(':');
// get a list of configs for just this alt
// it will help us print better later
List configsInAlt = new ArrayList();
for (ATNConfig c : configurations) {
if ( c.getAlt()!=alt ) continue;
configsInAlt.add(c);
}
int n = 0;
for (int cIndex = 0; cIndex < configsInAlt.size(); cIndex++) {
ATNConfig c = configsInAlt.get(cIndex);
n++;
buf.append(c.toString(null, false));
if ( (cIndex+1)3 ) {
buf.append("\\n");
}
}
}
}
String stateLabel = buf.toString();
return stateLabel;
}
public String getDOT(ATNState startState) {
return getDOT(startState, false);
}
public String getDOT(ATNState startState, boolean isLexer) {
Set ruleNames = grammar.rules.keySet();
String[] names = new String[ruleNames.size()+1];
int i = 0;
for (String s : ruleNames) names[i++] = s;
return getDOT(startState, names, isLexer);
}
/** 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.
*/
public String getDOT(ATNState startState, String[] ruleNames, boolean isLexer) {
if ( startState==null ) return null;
// The output DOT graph for visualization
Set markedStates = new HashSet();
ST dot = stlib.getInstanceOf("atn");
dot.add("startState", startState.stateNumber);
dot.add("rankdir", rankdir);
List work = new LinkedList();
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");
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(String.valueOf((char)atom.label))+"'";
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.getTokenNames());
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.getTokenNames());
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();
}
/** Do a depth-first walk of the state machine graph and
* fill a DOT description template. Keep filling the
* states and edges attributes. We know this is an ATN
* for a rule so don't traverse edges to other rules and
* don't go past rule end state.
*/
// protected void walkRuleATNCreatingDOT(ST dot,
// ATNState s)
// {
// if ( markedStates.contains(s) ) {
// return; // already visited this node
// }
//
// markedStates.add(s.stateNumber); // mark this node as completed.
//
// // first add this node
// ST stateST;
// if ( s instanceof RuleStopState ) {
// stateST = stlib.getInstanceOf("stopstate");
// }
// else {
// stateST = stlib.getInstanceOf("state");
// }
// stateST.add("name", getStateLabel(s));
// dot.add("states", stateST);
//
// if ( s instanceof RuleStopState ) {
// return; // don't go past end of rule node to the follow states
// }
//
// // special case: if decision point, then line up the alt start states
// // unless it's an end of block
// if ( s instanceof DecisionState ) {
// GrammarAST n = ((ATNState)s).ast;
// if ( n!=null && s instanceof BlockEndState ) {
// ST rankST = stlib.getInstanceOf("decision-rank");
// ATNState alt = (ATNState)s;
// while ( alt!=null ) {
// rankST.add("states", getStateLabel(alt));
// if ( alt.transition(1) !=null ) {
// alt = (ATNState)alt.transition(1).target;
// }
// else {
// alt=null;
// }
// }
// dot.add("decisionRanks", rankST);
// }
// }
//
// // make a DOT edge for each transition
// ST edgeST = null;
// for (int i = 0; i < s.getNumberOfTransitions(); i++) {
// Transition edge = (Transition) 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");
// if ( rr.rule.g != grammar ) {
// edgeST.add("label", "<"+rr.rule.g.name+"."+rr.rule.name+">");
// }
// else {
// edgeST.add("label", "<"+rr.rule.name+">");
// }
// edgeST.add("src", getStateLabel(s));
// edgeST.add("target", getStateLabel(rr.followState));
// edgeST.add("arrowhead", arrowhead);
// dot.add("edges", edgeST);
// walkRuleATNCreatingDOT(dot, rr.followState);
// continue;
// }
// if ( edge instanceof ActionTransition ) {
// edgeST = stlib.getInstanceOf("action-edge");
// }
// else if ( edge instanceof PredicateTransition ) {
// edgeST = stlib.getInstanceOf("edge");
// }
// else if ( edge.isEpsilon() ) {
// edgeST = stlib.getInstanceOf("epsilon-edge");
// }
// else {
// edgeST = stlib.getInstanceOf("edge");
// }
// edgeST.add("label", getEdgeLabel(edge.toString(grammar)));
// edgeST.add("src", getStateLabel(s));
// edgeST.add("target", getStateLabel(edge.target));
// edgeST.add("arrowhead", arrowhead);
// dot.add("edges", edgeST);
// walkRuleATNCreatingDOT(dot, edge.target); // keep walkin'
// }
// }
/** Fix edge strings so they print out in DOT properly;
* generate any gated predicates on edge too.
*/
protected String getEdgeLabel(String label) {
label = Utils.replace(label,"\\", "\\\\");
label = Utils.replace(label,"\"", "\\\"");
label = Utils.replace(label,"\n", "\\\\n");
label = Utils.replace(label,"\r", "");
return label;
}
protected String getStateLabel(ATNState s) {
if ( s==null ) return "null";
String stateLabel = "";
if (s instanceof BlockStartState) {
stateLabel += "→\\n";
}
else if (s instanceof BlockEndState) {
stateLabel += "←\\n";
}
stateLabel += String.valueOf(s.stateNumber);
if (s instanceof PlusBlockStartState || s instanceof PlusLoopbackState) {
stateLabel += "+";
}
else if (s instanceof StarBlockStartState || s instanceof StarLoopEntryState || s instanceof StarLoopbackState) {
stateLabel += "*";
}
if ( s instanceof DecisionState && ((DecisionState)s).decision>=0 ) {
stateLabel = stateLabel+"\\nd="+((DecisionState)s).decision;
}
return stateLabel;
}
}