1 /* 2 * [The "BSD license"] 3 * Copyright (c) 2010 Terence Parr 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 3. The name of the author may not be used to endorse or promote products 15 * derived from this software without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 package org.antlr.tool; 29 30 import org.antlr.Tool; 31 import org.antlr.analysis.*; 32 import org.antlr.grammar.v3.ANTLRParser; 33 import org.antlr.misc.Utils; 34 import org.stringtemplate.v4.ST; 35 import org.stringtemplate.v4.STGroup; 36 import org.stringtemplate.v4.STGroupDir; 37 38 import java.util.*; 39 40 /** The DOT (part of graphviz) generation aspect. */ 41 public class DOTGenerator { 42 public static final boolean STRIP_NONREDUCED_STATES = false; 43 44 protected String arrowhead="normal"; 45 protected String rankdir="LR"; 46 47 /** Library of output templates; use <attrname> format */ 48 public static STGroup stlib = new STGroupDir("org/antlr/tool/templates/dot/dfa"); 49 50 /** To prevent infinite recursion when walking state machines, record 51 * which states we've visited. Make a new set every time you start 52 * walking in case you reuse this object. 53 */ 54 protected Set markedStates = null; 55 56 protected Grammar grammar; 57 58 /** This aspect is associated with a grammar */ DOTGenerator(Grammar grammar)59 public DOTGenerator(Grammar grammar) { 60 this.grammar = grammar; 61 } 62 63 /** Return a String containing a DOT description that, when displayed, 64 * will show the incoming state machine visually. All nodes reachable 65 * from startState will be included. 66 */ getDOT(State startState)67 public String getDOT(State startState) { 68 if ( startState==null ) { 69 return null; 70 } 71 // The output DOT graph for visualization 72 ST dot = null; 73 markedStates = new HashSet(); 74 if ( startState instanceof DFAState ) { 75 dot = stlib.getInstanceOf("dfa"); 76 dot.add("startState", 77 Utils.integer(startState.stateNumber)); 78 dot.add("useBox", 79 Boolean.valueOf(Tool.internalOption_ShowNFAConfigsInDFA)); 80 walkCreatingDFADOT(dot, (DFAState)startState); 81 } 82 else { 83 dot = stlib.getInstanceOf("nfa"); 84 dot.add("startState", 85 Utils.integer(startState.stateNumber)); 86 walkRuleNFACreatingDOT(dot, startState); 87 } 88 dot.add("rankdir", rankdir); 89 return dot.toString(); 90 } 91 92 /** Return a String containing a DOT description that, when displayed, 93 * will show the incoming state machine visually. All nodes reachable 94 * from startState will be included. 95 public String getRuleNFADOT(State startState) { 96 // The output DOT graph for visualization 97 ST dot = stlib.getInstanceOf("nfa"); 98 99 markedStates = new HashSet(); 100 dot.add("startState", 101 Utils.integer(startState.stateNumber)); 102 walkRuleNFACreatingDOT(dot, startState); 103 return dot.toString(); 104 } 105 */ 106 107 /** Do a depth-first walk of the state machine graph and 108 * fill a DOT description template. Keep filling the 109 * states and edges attributes. 110 */ walkCreatingDFADOT(ST dot, DFAState s)111 protected void walkCreatingDFADOT(ST dot, 112 DFAState s) 113 { 114 if ( markedStates.contains(Utils.integer(s.stateNumber)) ) { 115 return; // already visited this node 116 } 117 118 markedStates.add(Utils.integer(s.stateNumber)); // mark this node as completed. 119 120 // first add this node 121 ST st; 122 if ( s.isAcceptState() ) { 123 st = stlib.getInstanceOf("stopstate"); 124 } 125 else { 126 st = stlib.getInstanceOf("state"); 127 } 128 st.add("name", getStateLabel(s)); 129 dot.add("states", st); 130 131 // make a DOT edge for each transition 132 for (int i = 0; i < s.getNumberOfTransitions(); i++) { 133 Transition edge = (Transition) s.transition(i); 134 /* 135 System.out.println("dfa "+s.dfa.decisionNumber+ 136 " edge from s"+s.stateNumber+" ["+i+"] of "+s.getNumberOfTransitions()); 137 */ 138 if ( STRIP_NONREDUCED_STATES ) { 139 if ( edge.target instanceof DFAState && 140 ((DFAState)edge.target).getAcceptStateReachable()!=DFA.REACHABLE_YES ) 141 { 142 continue; // don't generate nodes for terminal states 143 } 144 } 145 st = stlib.getInstanceOf("edge"); 146 st.add("label", getEdgeLabel(edge)); 147 st.add("src", getStateLabel(s)); 148 st.add("target", getStateLabel(edge.target)); 149 st.add("arrowhead", arrowhead); 150 dot.add("edges", st); 151 walkCreatingDFADOT(dot, (DFAState)edge.target); // keep walkin' 152 } 153 } 154 155 /** Do a depth-first walk of the state machine graph and 156 * fill a DOT description template. Keep filling the 157 * states and edges attributes. We know this is an NFA 158 * for a rule so don't traverse edges to other rules and 159 * don't go past rule end state. 160 */ walkRuleNFACreatingDOT(ST dot, State s)161 protected void walkRuleNFACreatingDOT(ST dot, 162 State s) 163 { 164 if ( markedStates.contains(s) ) { 165 return; // already visited this node 166 } 167 168 markedStates.add(s); // mark this node as completed. 169 170 // first add this node 171 ST stateST; 172 if ( s.isAcceptState() ) { 173 stateST = stlib.getInstanceOf("stopstate"); 174 } 175 else { 176 stateST = stlib.getInstanceOf("state"); 177 } 178 stateST.add("name", getStateLabel(s)); 179 dot.add("states", stateST); 180 181 if ( s.isAcceptState() ) { 182 return; // don't go past end of rule node to the follow states 183 } 184 185 // special case: if decision point, then line up the alt start states 186 // unless it's an end of block 187 if ( ((NFAState)s).isDecisionState() ) { 188 GrammarAST n = ((NFAState)s).associatedASTNode; 189 if ( n!=null && n.getType()!=ANTLRParser.EOB ) { 190 ST rankST = stlib.getInstanceOf("decision-rank"); 191 NFAState alt = (NFAState)s; 192 while ( alt!=null ) { 193 rankST.add("states", getStateLabel(alt)); 194 if ( alt.transition[1] !=null ) { 195 alt = (NFAState)alt.transition[1].target; 196 } 197 else { 198 alt=null; 199 } 200 } 201 dot.add("decisionRanks", rankST); 202 } 203 } 204 205 // make a DOT edge for each transition 206 ST edgeST = null; 207 for (int i = 0; i < s.getNumberOfTransitions(); i++) { 208 Transition edge = (Transition) s.transition(i); 209 if ( edge instanceof RuleClosureTransition ) { 210 RuleClosureTransition rr = ((RuleClosureTransition)edge); 211 // don't jump to other rules, but display edge to follow node 212 edgeST = stlib.getInstanceOf("edge"); 213 if ( rr.rule.grammar != grammar ) { 214 edgeST.add("label", "<" + rr.rule.grammar.name + "." + rr.rule.name + ">"); 215 } 216 else { 217 edgeST.add("label", "<" + rr.rule.name + ">"); 218 } 219 edgeST.add("src", getStateLabel(s)); 220 edgeST.add("target", getStateLabel(rr.followState)); 221 edgeST.add("arrowhead", arrowhead); 222 dot.add("edges", edgeST); 223 walkRuleNFACreatingDOT(dot, rr.followState); 224 continue; 225 } 226 if ( edge.isAction() ) { 227 edgeST = stlib.getInstanceOf("action-edge"); 228 } 229 else if ( edge.isEpsilon() ) { 230 edgeST = stlib.getInstanceOf("epsilon-edge"); 231 } 232 else { 233 edgeST = stlib.getInstanceOf("edge"); 234 } 235 edgeST.add("label", getEdgeLabel(edge)); 236 edgeST.add("src", getStateLabel(s)); 237 edgeST.add("target", getStateLabel(edge.target)); 238 edgeST.add("arrowhead", arrowhead); 239 dot.add("edges", edgeST); 240 walkRuleNFACreatingDOT(dot, edge.target); // keep walkin' 241 } 242 } 243 244 /* 245 public void writeDOTFilesForAllRuleNFAs() throws IOException { 246 Collection rules = grammar.getRules(); 247 for (Iterator itr = rules.iterator(); itr.hasNext();) { 248 Grammar.Rule r = (Grammar.Rule) itr.next(); 249 String ruleName = r.name; 250 writeDOTFile( 251 ruleName, 252 getRuleNFADOT(grammar.getRuleStartState(ruleName))); 253 } 254 } 255 */ 256 257 /* 258 public void writeDOTFilesForAllDecisionDFAs() throws IOException { 259 // for debugging, create a DOT file for each decision in 260 // a directory named for the grammar. 261 File grammarDir = new File(grammar.name+"_DFAs"); 262 grammarDir.mkdirs(); 263 List decisionList = grammar.getDecisionNFAStartStateList(); 264 if ( decisionList==null ) { 265 return; 266 } 267 int i = 1; 268 Iterator iter = decisionList.iterator(); 269 while (iter.hasNext()) { 270 NFAState decisionState = (NFAState)iter.next(); 271 DFA dfa = decisionState.getDecisionASTNode().getLookaheadDFA(); 272 if ( dfa!=null ) { 273 String dot = getDOT( dfa.startState ); 274 writeDOTFile(grammarDir+"/dec-"+i, dot); 275 } 276 i++; 277 } 278 } 279 */ 280 281 /** Fix edge strings so they print out in DOT properly; 282 * generate any gated predicates on edge too. 283 */ getEdgeLabel(Transition edge)284 protected String getEdgeLabel(Transition edge) { 285 String label = edge.label.toString(grammar); 286 label = Utils.replace(label,"\\", "\\\\"); 287 label = Utils.replace(label,"\"", "\\\""); 288 label = Utils.replace(label,"\n", "\\\\n"); 289 label = Utils.replace(label,"\r", ""); 290 if ( label.equals(Label.EPSILON_STR) ) { 291 label = "e"; 292 } 293 State target = edge.target; 294 if ( !edge.isSemanticPredicate() && target instanceof DFAState ) { 295 // look for gated predicates; don't add gated to simple sempred edges 296 SemanticContext preds = 297 ((DFAState)target).getGatedPredicatesInNFAConfigurations(); 298 if ( preds!=null ) { 299 String predsStr = ""; 300 predsStr = "&&{"+ 301 preds.genExpr(grammar.generator, 302 grammar.generator.getTemplates(), null).toString() 303 +"}?"; 304 label += predsStr; 305 } 306 } 307 return label; 308 } 309 getStateLabel(State s)310 protected String getStateLabel(State s) { 311 if ( s==null ) { 312 return "null"; 313 } 314 String stateLabel = String.valueOf(s.stateNumber); 315 if ( s instanceof DFAState ) { 316 StringBuffer buf = new StringBuffer(250); 317 buf.append('s'); 318 buf.append(s.stateNumber); 319 if ( Tool.internalOption_ShowNFAConfigsInDFA ) { 320 if ( s instanceof DFAState ) { 321 if ( ((DFAState)s).abortedDueToRecursionOverflow ) { 322 buf.append("\\n"); 323 buf.append("abortedDueToRecursionOverflow"); 324 } 325 } 326 Set alts = ((DFAState)s).getAltSet(); 327 if ( alts!=null ) { 328 buf.append("\\n"); 329 // separate alts 330 List altList = new ArrayList(); 331 altList.addAll(alts); 332 Collections.sort(altList); 333 Set configurations = ((DFAState) s).nfaConfigurations; 334 for (int altIndex = 0; altIndex < altList.size(); altIndex++) { 335 Integer altI = (Integer) altList.get(altIndex); 336 int alt = altI.intValue(); 337 if ( altIndex>0 ) { 338 buf.append("\\n"); 339 } 340 buf.append("alt"); 341 buf.append(alt); 342 buf.append(':'); 343 // get a list of configs for just this alt 344 // it will help us print better later 345 List configsInAlt = new ArrayList(); 346 for (Iterator it = configurations.iterator(); it.hasNext();) { 347 NFAConfiguration c = (NFAConfiguration) it.next(); 348 if ( c.alt!=alt ) continue; 349 configsInAlt.add(c); 350 } 351 int n = 0; 352 for (int cIndex = 0; cIndex < configsInAlt.size(); cIndex++) { 353 NFAConfiguration c = 354 (NFAConfiguration)configsInAlt.get(cIndex); 355 n++; 356 buf.append(c.toString(false)); 357 if ( (cIndex+1)<configsInAlt.size() ) { 358 buf.append(", "); 359 } 360 if ( n%5==0 && (configsInAlt.size()-cIndex)>3 ) { 361 buf.append("\\n"); 362 } 363 } 364 } 365 } 366 } 367 stateLabel = buf.toString(); 368 } 369 if ( (s instanceof NFAState) && ((NFAState)s).isDecisionState() ) { 370 stateLabel = stateLabel+",d="+ 371 ((NFAState)s).getDecisionNumber(); 372 if ( ((NFAState)s).endOfBlockStateNumber!=State.INVALID_STATE_NUMBER ) { 373 stateLabel += ",eob="+((NFAState)s).endOfBlockStateNumber; 374 } 375 } 376 else if ( (s instanceof NFAState) && 377 ((NFAState)s).endOfBlockStateNumber!=State.INVALID_STATE_NUMBER) 378 { 379 NFAState n = ((NFAState)s); 380 stateLabel = stateLabel+",eob="+n.endOfBlockStateNumber; 381 } 382 else if ( s instanceof DFAState && ((DFAState)s).isAcceptState() ) { 383 stateLabel = stateLabel+ 384 "=>"+((DFAState)s).getUniquelyPredictedAlt(); 385 } 386 return '"'+stateLabel+'"'; 387 } 388 getArrowheadType()389 public String getArrowheadType() { 390 return arrowhead; 391 } 392 setArrowheadType(String arrowhead)393 public void setArrowheadType(String arrowhead) { 394 this.arrowhead = arrowhead; 395 } 396 getRankdir()397 public String getRankdir() { 398 return rankdir; 399 } 400 setRankdir(String rankdir)401 public void setRankdir(String rankdir) { 402 this.rankdir = rankdir; 403 } 404 } 405