made DOTGen nonrecursive; add DFA verification stuff

[git-p4: depot-paths = "//depot/code/antlr4/main/": change = 6749]
This commit is contained in:
parrt 2010-03-13 13:04:18 -08:00
parent bf9c0dd5a2
commit f242c09d46
9 changed files with 453 additions and 220 deletions

View File

@ -1,3 +1,3 @@
state(state, name) ::= <<
node [fontsize=11, <if(useBox)>shape=box, fixedsize=false<else>shape=circle, fixedsize=true, width=.4<endif>]; <name>
node [fontsize=11, label="<label>", <if(useBox)>shape=box, fixedsize=false<else>shape=circle, fixedsize=true, width=.4<endif>, peripheries=1]; <name>
>>

View File

@ -1,3 +1,3 @@
stopstate(name) ::= <<
node [fontsize=11, <if(useBox)>shape=polygon,sides=4,peripheries=2,fixedsize=false<else>shape=doublecircle, fixedsize=true, width=.6<endif>]; <name>
node [fontsize=11, label="<label>", <if(useBox)>shape=polygon,sides=4,peripheries=2,fixedsize=false<else>shape=doublecircle, fixedsize=true, width=.6<endif>]; <name>
>>

View File

@ -1,9 +1,12 @@
package org.antlr.v4.analysis;
import org.antlr.v4.automata.DFA;
import org.antlr.v4.automata.DFAState;
import org.antlr.v4.automata.Edge;
import org.antlr.v4.misc.Utils;
import org.stringtemplate.v4.misc.MultiMap;
import java.util.HashSet;
import java.util.Set;
import java.util.*;
/** Detect imperfect DFA:
*
@ -12,7 +15,17 @@ import java.util.Set;
* 3. nondeterministic states
*/
public class DFAVerifier {
public static enum ReachableStatus {
UNKNOWN,
BUSY, // in process of computing
NO,
YES;
}
Map<DFAState, ReachableStatus> status = new HashMap<DFAState, ReachableStatus>();
DFA dfa;
StackLimitedNFAToDFAConverter converter;
public DFAVerifier(DFA dfa, StackLimitedNFAToDFAConverter converter) {
@ -20,11 +33,128 @@ public class DFAVerifier {
this.converter = converter;
}
public void analyze() {
}
public Set<Integer> getUnreachableAlts() {
return new HashSet<Integer>();
Set<Integer> unreachable = new HashSet<Integer>();
for (int alt=0; alt<dfa.nAlts; alt++) {
if ( dfa.altToAcceptState[alt]==null ) unreachable.add(alt);
}
return unreachable;
}
public Set<DFAState> getDeadStates() {
// create 2D matrix showing incident edges; inverse of adjacency matrix
// incidentEdges.get(s) is list of edges pointing at state s
MultiMap<DFAState, DFAState> incidentStates = new MultiMap<DFAState, DFAState>();
for (DFAState d : dfa.states.values()) {
for (Edge e : d.edges) incidentStates.map(e.target, d);
}
//Set<DFAState> reaches = new HashSet<DFAState>(dfa.uniqueStates.size());
Set<DFAState> dead = new HashSet<DFAState>(dfa.states.size());
dead.addAll(dfa.states.values());
for (DFAState a : dfa.altToAcceptState) {
if ( a!=null ) dead.remove(a);
}
// obviously accept states reach accept states
//reaches.addAll(Arrays.asList(dfa.altToAcceptState));
boolean changed = true;
while ( changed ) {
changed = false;
for (DFAState d : dfa.states.values()) {
if ( !dead.contains(d) ) {
// if d isn't dead, it reaches accept state.
dead.remove(d);
// and, so do all states pointing at it
List<DFAState> incoming = incidentStates.get(d);
if ( incoming!=null ) dead.removeAll(incoming);
changed = true;
}
}
}
// boolean changed = true;
// while ( changed ) {
// changed = false;
// for (DFAState d : dfa.uniqueStates.values()) {
// if ( reaches.contains(d) ) {
// dead.remove(d);
// // if d reaches, so do all states pointing at it
// for (DFAState i : incidentStates.get(d)) {
// if ( !reaches.contains(i) ) {
// reaches.add(i);
// changed = true;
// }
// }
// }
// }
// }
System.out.println("dead="+dead);
return dead;
}
/** figure out if this state eventually reaches an accept state and
* modify the instance variable 'reduced' to indicate if we find
* at least one state that cannot reach an accept state. This implies
* that the overall DFA is not reduced. This algorithm should be
* linear in the number of DFA states.
*
* The algorithm also tracks which alternatives have no accept state,
* indicating a nondeterminism.
*
* Also computes whether the DFA is cyclic.
*
* TODO: I call getUniquelyPredicatedAlt too much; cache predicted alt
* TODO: convert to nonrecursive version.
*/
boolean _verify(DFAState d) { // TODO: rename to verify?
if ( d.isAcceptState ) {
// accept states have no edges emanating from them so we can return
status.put(d, ReachableStatus.YES);
// this alt is uniquely predicted, remove from nondeterministic list
int predicts = d.getUniquelyPredictedAlt();
converter.unreachableAlts.remove(Utils.integer(predicts));
return true;
}
// avoid infinite loops
status.put(d, ReachableStatus.BUSY);
boolean anEdgeReachesAcceptState = false;
// Visit every transition, track if at least one edge reaches stop state
// Cannot terminate when we know this state reaches stop state since
// all transitions must be traversed to set status of each DFA state.
for (int i=0; i<d.getNumberOfTransitions(); i++) {
Edge t = d.transition(i);
DFAState edgeTarget = (DFAState)t.target;
ReachableStatus targetStatus = status.get(edgeTarget);
if ( targetStatus==ReachableStatus.BUSY ) { // avoid cycles; they say nothing
converter.cyclic = true;
continue;
}
if ( targetStatus==ReachableStatus.YES ) { // avoid unnecessary work
anEdgeReachesAcceptState = true;
continue;
}
if ( targetStatus==ReachableStatus.NO ) { // avoid unnecessary work
continue;
}
// target must be ReachableStatus.UNKNOWN (i.e., unvisited)
if ( _verify(edgeTarget) ) {
anEdgeReachesAcceptState = true;
// have to keep looking so don't break loop
// must cover all states even if we find a path for this state
}
}
if ( anEdgeReachesAcceptState ) {
status.put(d, ReachableStatus.YES);
}
else {
status.put(d, ReachableStatus.NO);
converter.reduced = false;
}
return anEdgeReachesAcceptState;
}
}

View File

@ -26,7 +26,8 @@ public class Resolver {
* conflicting ctx predicts alts i and j. Return an Integer set
* of the alternative numbers that conflict. Two contexts conflict if
* they are equal or one is a stack suffix of the other or one is
* the empty context.
* the empty context. The conflict is a true ambiguity. No amount
* of further looking in grammar will resolve issue (only preds help).
*
* Use a hash table to record the lists of configs for each state
* as they are encountered. We need only consider states for which
@ -34,9 +35,9 @@ public class Resolver {
* alt must be different or must have different contexts to avoid a
* conflict.
*/
public Set<Integer> getNonDeterministicAlts(DFAState d) {
public Set<Integer> getAmbiguousAlts(DFAState d) {
//System.out.println("getNondetAlts for DFA state "+stateNumber);
Set<Integer> nondeterministicAlts = new HashSet<Integer>();
Set<Integer> ambiguousAlts = new HashSet<Integer>();
// If only 1 NFA conf then no way it can be nondeterministic;
// save the overhead. There are many o-a->o NFA transitions
@ -54,7 +55,8 @@ public class Resolver {
}
// potential conflicts are states with > 1 configuration and diff alts
boolean thisStateHasPotentialProblem = false;
// boolean thisStateHasPotentialProblem = false;
boolean deterministic = true;
for (List<NFAConfig> configsForState : stateToConfigListMap.values()) {
if ( configsForState.size()>1 ) {
int predictedAlt = Resolver.getUniqueAlt(configsForState, false);
@ -65,13 +67,19 @@ public class Resolver {
stateToConfigListMap.put(configsForState.get(0).state.stateNumber, null);
}
else {
thisStateHasPotentialProblem = true;
//thisStateHasPotentialProblem = true;
deterministic = false;
}
}
}
// a fast check for potential issues; most states have none
if ( !thisStateHasPotentialProblem ) return null;
// if ( !thisStateHasPotentialProblem ) return null;
if ( deterministic ) {
d.isAcceptState = true;
return null;
}
// we have a potential problem, so now go through config lists again
// looking for different alts (only states with potential issues
@ -107,35 +115,34 @@ public class Resolver {
ctxConflict = s.context.conflictsWith(t.context);
}
if ( altConflict && ctxConflict ) {
nondeterministicAlts.add(s.alt);
nondeterministicAlts.add(t.alt);
ambiguousAlts.add(s.alt);
ambiguousAlts.add(t.alt);
}
}
}
}
if ( nondeterministicAlts.size()==0 ) return null;
return nondeterministicAlts;
if ( ambiguousAlts.size()==0 ) return null;
return ambiguousAlts;
}
public void resolveNonDeterminisms(DFAState d) {
public void resolveAmbiguities(DFAState d) {
if ( StackLimitedNFAToDFAConverter.debug ) {
System.out.println("resolveNonDeterminisms "+d.toString());
}
Set<Integer> nondeterministicAlts = getNonDeterministicAlts(d);
if ( StackLimitedNFAToDFAConverter.debug && nondeterministicAlts!=null ) {
System.out.println("nondet alts="+nondeterministicAlts);
Set<Integer> ambiguousAlts = getAmbiguousAlts(d);
if ( StackLimitedNFAToDFAConverter.debug && ambiguousAlts!=null ) {
System.out.println("ambig alts="+ambiguousAlts);
}
// if no problems return
if ( nondeterministicAlts==null ) return;
if ( ambiguousAlts==null ) return;
// reportNondeterminism(d, nondeterministicAlts);
converter.nondeterministicStates.add(d);
converter.ambiguousStates.add(d);
// ATTEMPT TO RESOLVE WITH SEMANTIC PREDICATES
boolean resolved =
semResolver.tryToResolveWithSemanticPredicates(d, nondeterministicAlts);
semResolver.tryToResolveWithSemanticPredicates(d, ambiguousAlts);
if ( resolved ) {
if ( StackLimitedNFAToDFAConverter.debug ) {
System.out.println("resolved DFA state "+d.stateNumber+" with pred");
@ -146,7 +153,7 @@ public class Resolver {
}
// RESOLVE SYNTACTIC CONFLICT BY REMOVING ALL BUT ONE ALT
resolveByPickingMinAlt(d, nondeterministicAlts);
resolveByPickingMinAlt(d, ambiguousAlts);
}
@ -166,39 +173,39 @@ public class Resolver {
}
/** Turn off all configurations associated with the
* set of incoming nondeterministic alts except the min alt number.
* set of incoming alts except the min alt number.
* There may be many alts among the configurations but only turn off
* the ones with problems (other than the min alt of course).
*
* If nondeterministicAlts is null then turn off all configs 'cept those
* If alts is null then turn off all configs 'cept those
* associated with the minimum alt.
*
* Return the min alt found.
*/
int resolveByPickingMinAlt(DFAState d, Set<Integer> nondeterministicAlts) {
int resolveByPickingMinAlt(DFAState d, Set<Integer> alts) {
int min = Integer.MAX_VALUE;
if ( nondeterministicAlts!=null ) {
min = getMinAlt(nondeterministicAlts);
if ( alts !=null ) {
min = getMinAlt(alts);
}
else {
min = d.getMinAlt();
}
turnOffOtherAlts(d, min, nondeterministicAlts);
turnOffOtherAlts(d, min, alts);
return min;
}
/** turn off all states associated with alts other than the good one
* (as long as they are one of the nondeterministic ones)
* (as long as they are one of the ones in alts)
*/
void turnOffOtherAlts(DFAState d, int min, Set<Integer> nondeterministicAlts) {
void turnOffOtherAlts(DFAState d, int min, Set<Integer> alts) {
int numConfigs = d.nfaConfigs.size();
for (int i = 0; i < numConfigs; i++) {
NFAConfig configuration = d.nfaConfigs.get(i);
if ( configuration.alt!=min ) {
if ( nondeterministicAlts==null ||
nondeterministicAlts.contains(configuration.alt) )
if ( alts==null ||
alts.contains(configuration.alt) )
{
configuration.resolved = true;
}
@ -206,9 +213,9 @@ public class Resolver {
}
}
public static int getMinAlt(Set<Integer> nondeterministicAlts) {
public static int getMinAlt(Set<Integer> alts) {
int min = Integer.MAX_VALUE;
for (Integer altI : nondeterministicAlts) {
for (Integer altI : alts) {
int alt = altI.intValue();
if ( alt < min ) min = alt;
}

View File

@ -33,25 +33,28 @@ public class StackLimitedNFAToDFAConverter {
* lead to this situation (assuming no semantic predicates can resolve
* the problem) and when for some reason, I cannot compute the lookahead
* (which might arise from an error in the algorithm or from
* left-recursion etc...). This list starts out with all alts contained
* and then in method doesStateReachAcceptState() I remove the alts I
* know to be uniquely predicted.
* left-recursion etc...).
*/
public List<Integer> unreachableAlts;
public Set<Integer> unreachableAlts;
/** Track all DFA states with nondeterministic alternatives.
/** Track all DFA states with ambiguous configurations.
* By reaching the same DFA state, a path through the NFA for some input
* is able to reach the same NFA state by starting at more than one
* alternative's left edge. Though, later, we may find that predicates
* resolve the issue, but track info anyway.
* Note that from the DFA state, you can ask for
* which alts are nondeterministic.
* alternative's left edge. If the context is the same or conflicts,
* then we have ambiguity. If the context is different, it's simply
* nondeterministic and we should keep looking for edges that will
* render it deterministic. If we run out of things to add to the DFA,
* we'll get a dangling state; it's non-LL(*). Later we may find that predicates
* resolve the issue, but track ambiguous states anyway.
*/
public Set<DFAState> nondeterministicStates = new HashSet<DFAState>();
public Set<DFAState> ambiguousStates = new HashSet<DFAState>();
/** The set of states w/o emanating edges (and w/o resolving sem preds). */
public Set<DFAState> danglingStates = new HashSet<DFAState>();
/** If non-reduced, this is set of states that don't lead to accept state */
public Set<DFAState> deadStates;
/** Was a syntactic ambiguity resolved with predicates? Any DFA
* state that predicts more than one alternative, must be resolved
* with predicates or it should be reported to the user.
@ -66,6 +69,12 @@ public class StackLimitedNFAToDFAConverter {
Set<DFAState> recursionOverflowStates = new HashSet<DFAState>();
/** Are there any loops in this DFA? Computed by DFAVerifier */
public boolean cyclic = false;
/** Is this DFA reduced? I.e., can all states lead to an accept state? */
public boolean reduced = true;
/** Used to prevent the closure operation from looping to itself and
* hence looping forever. Sensitive to the NFA state, the alt, and
* the stack context.
@ -74,6 +83,8 @@ public class StackLimitedNFAToDFAConverter {
Resolver resolver;
DFAVerifier verifier;
public static boolean debug = false;
public StackLimitedNFAToDFAConverter(Grammar g, DecisionState nfaStartState) {
@ -82,10 +93,7 @@ public class StackLimitedNFAToDFAConverter {
dfa = new DFA(g, nfaStartState);
dfa.converter = this;
resolver = new Resolver(this);
unreachableAlts = new ArrayList<Integer>();
for (int i = 1; i <= dfa.nAlts; i++) {
unreachableAlts.add(i);
}
verifier = new DFAVerifier(dfa, this);
}
public DFA createDFA() {
@ -101,6 +109,9 @@ public class StackLimitedNFAToDFAConverter {
work.remove(0); // we're done with this DFA state
}
unreachableAlts = verifier.getUnreachableAlts();
//deadStates = verifier.getDeadStates();
return dfa;
}
@ -142,7 +153,7 @@ public class StackLimitedNFAToDFAConverter {
/** Add t if not in DFA yet, resolving nondet's and then make d-label->t */
void addTransition(DFAState d, IntervalSet label, DFAState t) {
DFAState existing = dfa.uniqueStates.get(t);
DFAState existing = dfa.states.get(t);
if ( existing != null ) { // seen before; point at old one
d.addTransition(new Edge(existing, label));
return;
@ -152,7 +163,7 @@ public class StackLimitedNFAToDFAConverter {
// resolve any syntactic conflicts by choosing a single alt or
// by using semantic predicates if present.
resolver.resolveNonDeterminisms(t);
resolver.resolveAmbiguities(t);
// If deterministic, don't add this state; it's an accept state
// Just return as a valid DFA state

View File

@ -28,7 +28,7 @@ public class DFA {
* Not used during fixed k lookahead as it's a waste to fill it with
* a dup of states array.
*/
public Map<DFAState, DFAState> uniqueStates = new HashMap<DFAState, DFAState>();
public Map<DFAState, DFAState> states = new HashMap<DFAState, DFAState>();
/** Maps the state number to the actual DFAState. This contains all
* states, but the states are not unique. s3 might be same as s1 so
@ -62,12 +62,12 @@ public class DFA {
/** Add a new DFA state to this DFA (doesn't check if already present). */
public void addState(DFAState d) {
uniqueStates.put(d,d);
states.put(d,d);
d.stateNumber = stateCounter++;
}
public void defineAcceptState(int alt, DFAState acceptState) {
if ( uniqueStates.get(acceptState)==null ) addState(acceptState);
if ( states.get(acceptState)==null ) addState(acceptState);
altToAcceptState[alt] = acceptState;
}
@ -80,7 +80,7 @@ public class DFA {
public boolean isDeterministic() {
if ( converter.danglingStates.size()==0 &&
converter.nondeterministicStates.size()==0 &&
converter.ambiguousStates.size()==0 &&
converter.unreachableAlts.size()==0 )
{
return true;

View File

@ -50,7 +50,7 @@ public class DFAState {
public DFA dfa;
/** Track the transitions emanating from this DFA state. */
protected List<Edge> edges =
public List<Edge> edges =
new ArrayList<Edge>(INITIAL_NUM_TRANSITIONS);
/** The set of NFA configurations (state,alt,context) for this DFA state */

View File

@ -11,6 +11,14 @@ public class NFAState {
/** NFAState created for which rule? */
public Rule rule;
/** Which NFA are we in? */
public NFA nfa = null;
/** NFA state is associated with which node in AST? */
public GrammarAST ast;
public NFAState(NFA nfa) { this.nfa = nfa; }
@Override
public int hashCode() {
return super.hashCode();
@ -28,14 +36,6 @@ public class NFAState {
return String.valueOf(stateNumber);
}
/** Which NFA are we in? */
public NFA nfa = null;
/** NFA state is associated with which node in AST? */
public GrammarAST ast;
public NFAState(NFA nfa) { this.nfa = nfa; }
public int getNumberOfTransitions() {
return 0;
}

View File

@ -20,12 +20,6 @@ public class DOTGenerator {
/** Library of output templates; use <attrname> format */
public static STGroup stlib = new STGroupDir("org/antlr/v4/tool/templates/dot");
/** 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<Integer> markedStates = null;
protected Grammar grammar;
/** This aspect is associated with a grammar */
@ -38,97 +32,22 @@ public class DOTGenerator {
* from startState will be included.
*/
public String getDOT(NFAState startState) {
if ( startState==null ) {
return null;
}
if ( startState==null ) return null;
// The output DOT graph for visualization
ST dot = null;
markedStates = new HashSet<Integer>();
Set<NFAState> markedStates = new HashSet<NFAState>();
dot = stlib.getInstanceOf("nfa");
dot.add("startState",
Utils.integer(startState.stateNumber));
walkRuleNFACreatingDOT(dot, startState);
dot.add("startState", Utils.integer(startState.stateNumber));
dot.add("rankdir", rankdir);
return dot.render();
}
public String getDOT(DFAState startState) {
if ( startState==null ) {
return null;
}
// The output DOT graph for visualization
ST dot = null;
markedStates = new HashSet<Integer>();
dot = stlib.getInstanceOf("dfa");
dot.add("startState", startState.stateNumber);
dot.add("useBox", Tool.internalOption_ShowNFAConfigsInDFA);
walkCreatingDFADOT(dot,startState);
dot.add("rankdir", rankdir);
return dot.render();
}
List<NFAState> work = new LinkedList<NFAState>();
/** 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++) {
Edge edge = s.transition(i);
/*
System.out.println("dfa "+s.dfa.decisionNumber+
" edge from s"+s.stateNumber+" ["+i+"] of "+s.getNumberOfTransitions());
*/
st = stlib.getInstanceOf("edge");
// SemanticContext preds = s.getGatedPredicatesInNFAConfigurations();
// if ( preds!=null ) {
// String predsStr = "";
// predsStr = "&&{"+preds.toString()+"}?";
// label += predsStr;
// }
st.add("label", getEdgeLabel(edge.toString(grammar)));
st.add("src", getStateLabel(s));
st.add("target", getStateLabel(edge.target));
st.add("arrowhead", arrowhead);
dot.add("edges", st);
walkCreatingDFADOT(dot, 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,
NFAState s)
{
if ( markedStates.contains(s) ) {
return; // already visited this node
}
markedStates.add(s.stateNumber); // mark this node as completed.
work.add(startState);
while ( work.size()>0 ) {
NFAState s = work.get(0);
if ( markedStates.contains(s) ) { work.remove(0); continue; }
markedStates.add(s);
// first add this node
ST stateST;
@ -139,11 +58,9 @@ public class DOTGenerator {
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
}
// 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
@ -153,7 +70,7 @@ public class DOTGenerator {
ST rankST = stlib.getInstanceOf("decision-rank");
NFAState alt = (NFAState)s;
while ( alt!=null ) {
rankST.add("states", getStateLabel(alt));
rankST.add("states", alt.stateNumber);
if ( alt.transition(1) !=null ) {
alt = (NFAState)alt.transition(1).target;
}
@ -179,11 +96,11 @@ public class DOTGenerator {
else {
edgeST.add("label", "<"+rr.rule.name+">");
}
edgeST.add("src", getStateLabel(s));
edgeST.add("target", getStateLabel(rr.followState));
edgeST.add("src", s.stateNumber);
edgeST.add("target", rr.followState.stateNumber);
edgeST.add("arrowhead", arrowhead);
dot.add("edges", edgeST);
walkRuleNFACreatingDOT(dot, rr.followState);
work.add(rr.followState);
continue;
}
if ( edge instanceof ActionTransition ) {
@ -199,14 +116,183 @@ public class DOTGenerator {
edgeST = stlib.getInstanceOf("edge");
}
edgeST.add("label", getEdgeLabel(edge.toString(grammar)));
edgeST.add("src", getStateLabel(s));
edgeST.add("target", getStateLabel(edge.target));
edgeST.add("src", s.stateNumber);
edgeST.add("target", edge.target.stateNumber);
edgeST.add("arrowhead", arrowhead);
dot.add("edges", edgeST);
walkRuleNFACreatingDOT(dot, edge.target); // keep walkin'
work.add(edge.target);
}
}
// define nodes we visited (they will appear first in DOT output)
// this is an example of ST's lazy eval :)
for (NFAState s : markedStates) {
ST st;
if ( s instanceof RuleStopState ) {
st = stlib.getInstanceOf("stopstate");
}
else {
st = stlib.getInstanceOf("state");
}
st.add("name", s.stateNumber);
st.add("label", getStateLabel(s));
dot.add("states", st);
}
return dot.render();
}
public String getDOT(DFAState startState) {
if ( startState==null ) return null;
ST dot = stlib.getInstanceOf("dfa");
dot.add("startState", startState.stateNumber);
dot.add("useBox", Tool.internalOption_ShowNFAConfigsInDFA);
dot.add("rankdir", rankdir);
Set<DFAState> markedStates = new HashSet<DFAState>();
List<DFAState> work = new LinkedList<DFAState>();
for (DFAState d : startState.dfa.states.values()) {
ST st;
if ( d.isAcceptState ) {
st = stlib.getInstanceOf("stopstate");
}
else {
st = stlib.getInstanceOf("state");
}
st.add("name", "s"+d.stateNumber);
st.add("label", getStateLabel(d));
dot.add("states", st);
}
work.add(startState);
while ( work.size()>0 ) {
DFAState d = work.get(0);
if ( markedStates.contains(d) ) { work.remove(0); continue; }
markedStates.add(d);
// make a DOT edge for each transition
for (int i = 0; i < d.getNumberOfTransitions(); i++) {
Edge edge = d.transition(i);
/*
System.out.println("dfa "+s.dfa.decisionNumber+
" edge from s"+s.stateNumber+" ["+i+"] of "+s.getNumberOfTransitions());
*/
ST st = stlib.getInstanceOf("edge");
// SemanticContext preds = s.getGatedPredicatesInNFAConfigurations();
// if ( preds!=null ) {
// String predsStr = "";
// predsStr = "&&{"+preds.toString()+"}?";
// label += predsStr;
// }
st.add("label", getEdgeLabel(edge.toString(grammar)));
st.add("src", "s"+d.stateNumber);
st.add("target", "s"+edge.target.stateNumber);
st.add("arrowhead", arrowhead);
dot.add("edges", st);
work.add(edge.target); // add targets to list of states to visit
}
work.remove(0);
}
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 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,
// NFAState 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 = ((NFAState)s).ast;
// if ( n!=null && s instanceof BlockEndState ) {
// 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 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);
// walkRuleNFACreatingDOT(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);
// walkRuleNFACreatingDOT(dot, edge.target); // keep walkin'
// }
// }
/** Fix edge strings so they print out in DOT properly;
* generate any gated predicates on edge too.
*/
@ -222,14 +308,13 @@ public class DOTGenerator {
if ( s==null ) return "null";
String stateLabel = String.valueOf(s.stateNumber);
if ( s instanceof DecisionState ) {
stateLabel = stateLabel+",d="+((DecisionState)s).decision;
stateLabel = stateLabel+"\\nd="+((DecisionState)s).decision;
}
return '"'+stateLabel+'"';
return stateLabel;
}
protected String getStateLabel(DFAState s) {
if ( s==null ) return "null";
String stateLabel = String.valueOf(s.stateNumber);
StringBuffer buf = new StringBuffer(250);
buf.append('s');
buf.append(s.stateNumber);
@ -275,10 +360,10 @@ public class DOTGenerator {
}
}
}
stateLabel = buf.toString();
String stateLabel = buf.toString();
if ( s.isAcceptState ) {
stateLabel = stateLabel+"=>"+s.getUniquelyPredictedAlt();
}
return '"'+stateLabel+'"';
return stateLabel;
}
}