| /* |
| * [The "BSD license"] |
| * Copyright (c) 2010 Terence Parr |
| * 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.tool; |
| |
| import org.antlr.Tool; |
| import org.antlr.analysis.*; |
| import org.antlr.grammar.v3.ANTLRParser; |
| import org.antlr.misc.Utils; |
| import org.stringtemplate.v4.ST; |
| import org.stringtemplate.v4.STGroup; |
| import org.stringtemplate.v4.STGroupDir; |
| |
| import java.util.*; |
| |
| /** 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 <attrname> format */ |
| public static STGroup stlib = new STGroupDir("org/antlr/tool/templates/dot/dfa"); |
| |
| /** To prevent infinite recursion when walking state machines, record |
| * which states we've visited. Make a new set every time you start |
| * walking in case you reuse this object. |
| */ |
| protected Set markedStates = null; |
| |
| protected Grammar grammar; |
| |
| /** This aspect is associated with a grammar */ |
| public DOTGenerator(Grammar grammar) { |
| this.grammar = grammar; |
| } |
| |
| /** 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(State startState) { |
| if ( startState==null ) { |
| return null; |
| } |
| // The output DOT graph for visualization |
| ST dot = null; |
| markedStates = new HashSet(); |
| if ( startState instanceof DFAState ) { |
| dot = stlib.getInstanceOf("dfa"); |
| dot.add("startState", |
| Utils.integer(startState.stateNumber)); |
| dot.add("useBox", |
| Boolean.valueOf(Tool.internalOption_ShowNFAConfigsInDFA)); |
| walkCreatingDFADOT(dot, (DFAState)startState); |
| } |
| else { |
| dot = stlib.getInstanceOf("nfa"); |
| dot.add("startState", |
| Utils.integer(startState.stateNumber)); |
| walkRuleNFACreatingDOT(dot, startState); |
| } |
| dot.add("rankdir", rankdir); |
| return dot.toString(); |
| } |
| |
| /** 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 getRuleNFADOT(State startState) { |
| // The output DOT graph for visualization |
| ST dot = stlib.getInstanceOf("nfa"); |
| |
| markedStates = new HashSet(); |
| dot.add("startState", |
| Utils.integer(startState.stateNumber)); |
| walkRuleNFACreatingDOT(dot, startState); |
| return dot.toString(); |
| } |
| */ |
| |
| /** Do a depth-first walk of the state machine graph and |
| * fill a DOT description template. Keep filling the |
| * states and edges attributes. |
| */ |
| protected void walkCreatingDFADOT(ST dot, |
| DFAState s) |
| { |
| if ( markedStates.contains(Utils.integer(s.stateNumber)) ) { |
| return; // already visited this node |
| } |
| |
| markedStates.add(Utils.integer(s.stateNumber)); // mark this node as completed. |
| |
| // first add this node |
| ST st; |
| if ( s.isAcceptState() ) { |
| st = stlib.getInstanceOf("stopstate"); |
| } |
| else { |
| st = stlib.getInstanceOf("state"); |
| } |
| st.add("name", getStateLabel(s)); |
| dot.add("states", st); |
| |
| // make a DOT edge for each transition |
| for (int i = 0; i < s.getNumberOfTransitions(); i++) { |
| Transition edge = (Transition) s.transition(i); |
| /* |
| System.out.println("dfa "+s.dfa.decisionNumber+ |
| " edge from s"+s.stateNumber+" ["+i+"] of "+s.getNumberOfTransitions()); |
| */ |
| if ( STRIP_NONREDUCED_STATES ) { |
| if ( edge.target instanceof DFAState && |
| ((DFAState)edge.target).getAcceptStateReachable()!=DFA.REACHABLE_YES ) |
| { |
| continue; // don't generate nodes for terminal states |
| } |
| } |
| st = stlib.getInstanceOf("edge"); |
| st.add("label", getEdgeLabel(edge)); |
| st.add("src", getStateLabel(s)); |
| st.add("target", getStateLabel(edge.target)); |
| st.add("arrowhead", arrowhead); |
| dot.add("edges", st); |
| walkCreatingDFADOT(dot, (DFAState)edge.target); // keep walkin' |
| } |
| } |
| |
| /** 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 NFA |
| * for a rule so don't traverse edges to other rules and |
| * don't go past rule end state. |
| */ |
| protected void walkRuleNFACreatingDOT(ST dot, |
| State s) |
| { |
| if ( markedStates.contains(s) ) { |
| return; // already visited this node |
| } |
| |
| markedStates.add(s); // mark this node as completed. |
| |
| // first add this node |
| ST stateST; |
| if ( s.isAcceptState() ) { |
| stateST = stlib.getInstanceOf("stopstate"); |
| } |
| else { |
| stateST = stlib.getInstanceOf("state"); |
| } |
| stateST.add("name", getStateLabel(s)); |
| dot.add("states", stateST); |
| |
| if ( s.isAcceptState() ) { |
| 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 ( ((NFAState)s).isDecisionState() ) { |
| GrammarAST n = ((NFAState)s).associatedASTNode; |
| if ( n!=null && n.getType()!=ANTLRParser.EOB ) { |
| ST rankST = stlib.getInstanceOf("decision-rank"); |
| NFAState alt = (NFAState)s; |
| while ( alt!=null ) { |
| rankST.add("states", getStateLabel(alt)); |
| if ( alt.transition[1] !=null ) { |
| alt = (NFAState)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 RuleClosureTransition ) { |
| RuleClosureTransition rr = ((RuleClosureTransition)edge); |
| // don't jump to other rules, but display edge to follow node |
| edgeST = stlib.getInstanceOf("edge"); |
| if ( rr.rule.grammar != grammar ) { |
| edgeST.add("label", "<" + rr.rule.grammar.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); |
| walkRuleNFACreatingDOT(dot, rr.followState); |
| continue; |
| } |
| if ( edge.isAction() ) { |
| edgeST = stlib.getInstanceOf("action-edge"); |
| } |
| else if ( edge.isEpsilon() ) { |
| edgeST = stlib.getInstanceOf("epsilon-edge"); |
| } |
| else { |
| edgeST = stlib.getInstanceOf("edge"); |
| } |
| edgeST.add("label", getEdgeLabel(edge)); |
| edgeST.add("src", getStateLabel(s)); |
| edgeST.add("target", getStateLabel(edge.target)); |
| edgeST.add("arrowhead", arrowhead); |
| dot.add("edges", edgeST); |
| walkRuleNFACreatingDOT(dot, edge.target); // keep walkin' |
| } |
| } |
| |
| /* |
| public void writeDOTFilesForAllRuleNFAs() throws IOException { |
| Collection rules = grammar.getRules(); |
| for (Iterator itr = rules.iterator(); itr.hasNext();) { |
| Grammar.Rule r = (Grammar.Rule) itr.next(); |
| String ruleName = r.name; |
| writeDOTFile( |
| ruleName, |
| getRuleNFADOT(grammar.getRuleStartState(ruleName))); |
| } |
| } |
| */ |
| |
| /* |
| public void writeDOTFilesForAllDecisionDFAs() throws IOException { |
| // for debugging, create a DOT file for each decision in |
| // a directory named for the grammar. |
| File grammarDir = new File(grammar.name+"_DFAs"); |
| grammarDir.mkdirs(); |
| List decisionList = grammar.getDecisionNFAStartStateList(); |
| if ( decisionList==null ) { |
| return; |
| } |
| int i = 1; |
| Iterator iter = decisionList.iterator(); |
| while (iter.hasNext()) { |
| NFAState decisionState = (NFAState)iter.next(); |
| DFA dfa = decisionState.getDecisionASTNode().getLookaheadDFA(); |
| if ( dfa!=null ) { |
| String dot = getDOT( dfa.startState ); |
| writeDOTFile(grammarDir+"/dec-"+i, dot); |
| } |
| i++; |
| } |
| } |
| */ |
| |
| /** Fix edge strings so they print out in DOT properly; |
| * generate any gated predicates on edge too. |
| */ |
| protected String getEdgeLabel(Transition edge) { |
| String label = edge.label.toString(grammar); |
| label = Utils.replace(label,"\\", "\\\\"); |
| label = Utils.replace(label,"\"", "\\\""); |
| label = Utils.replace(label,"\n", "\\\\n"); |
| label = Utils.replace(label,"\r", ""); |
| if ( label.equals(Label.EPSILON_STR) ) { |
| label = "e"; |
| } |
| State target = edge.target; |
| if ( !edge.isSemanticPredicate() && target instanceof DFAState ) { |
| // look for gated predicates; don't add gated to simple sempred edges |
| SemanticContext preds = |
| ((DFAState)target).getGatedPredicatesInNFAConfigurations(); |
| if ( preds!=null ) { |
| String predsStr = ""; |
| predsStr = "&&{"+ |
| preds.genExpr(grammar.generator, |
| grammar.generator.getTemplates(), null).toString() |
| +"}?"; |
| label += predsStr; |
| } |
| } |
| return label; |
| } |
| |
| protected String getStateLabel(State s) { |
| if ( s==null ) { |
| return "null"; |
| } |
| String stateLabel = String.valueOf(s.stateNumber); |
| if ( s instanceof DFAState ) { |
| StringBuffer buf = new StringBuffer(250); |
| buf.append('s'); |
| buf.append(s.stateNumber); |
| if ( Tool.internalOption_ShowNFAConfigsInDFA ) { |
| if ( s instanceof DFAState ) { |
| if ( ((DFAState)s).abortedDueToRecursionOverflow ) { |
| buf.append("\\n"); |
| buf.append("abortedDueToRecursionOverflow"); |
| } |
| } |
| Set alts = ((DFAState)s).getAltSet(); |
| if ( alts!=null ) { |
| buf.append("\\n"); |
| // separate alts |
| List altList = new ArrayList(); |
| altList.addAll(alts); |
| Collections.sort(altList); |
| Set configurations = ((DFAState) s).nfaConfigurations; |
| for (int altIndex = 0; altIndex < altList.size(); altIndex++) { |
| Integer altI = (Integer) altList.get(altIndex); |
| int alt = altI.intValue(); |
| if ( altIndex>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 (Iterator it = configurations.iterator(); it.hasNext();) { |
| NFAConfiguration c = (NFAConfiguration) it.next(); |
| if ( c.alt!=alt ) continue; |
| configsInAlt.add(c); |
| } |
| int n = 0; |
| for (int cIndex = 0; cIndex < configsInAlt.size(); cIndex++) { |
| NFAConfiguration c = |
| (NFAConfiguration)configsInAlt.get(cIndex); |
| n++; |
| buf.append(c.toString(false)); |
| if ( (cIndex+1)<configsInAlt.size() ) { |
| buf.append(", "); |
| } |
| if ( n%5==0 && (configsInAlt.size()-cIndex)>3 ) { |
| buf.append("\\n"); |
| } |
| } |
| } |
| } |
| } |
| stateLabel = buf.toString(); |
| } |
| if ( (s instanceof NFAState) && ((NFAState)s).isDecisionState() ) { |
| stateLabel = stateLabel+",d="+ |
| ((NFAState)s).getDecisionNumber(); |
| if ( ((NFAState)s).endOfBlockStateNumber!=State.INVALID_STATE_NUMBER ) { |
| stateLabel += ",eob="+((NFAState)s).endOfBlockStateNumber; |
| } |
| } |
| else if ( (s instanceof NFAState) && |
| ((NFAState)s).endOfBlockStateNumber!=State.INVALID_STATE_NUMBER) |
| { |
| NFAState n = ((NFAState)s); |
| stateLabel = stateLabel+",eob="+n.endOfBlockStateNumber; |
| } |
| else if ( s instanceof DFAState && ((DFAState)s).isAcceptState() ) { |
| stateLabel = stateLabel+ |
| "=>"+((DFAState)s).getUniquelyPredictedAlt(); |
| } |
| return '"'+stateLabel+'"'; |
| } |
| |
| public String getArrowheadType() { |
| return arrowhead; |
| } |
| |
| public void setArrowheadType(String arrowhead) { |
| this.arrowhead = arrowhead; |
| } |
| |
| public String getRankdir() { |
| return rankdir; |
| } |
| |
| public void setRankdir(String rankdir) { |
| this.rankdir = rankdir; |
| } |
| } |