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) ::= << 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) ::= << 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; package org.antlr.v4.analysis;
import org.antlr.v4.automata.DFA; 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.*;
import java.util.Set;
/** Detect imperfect DFA: /** Detect imperfect DFA:
* *
@ -12,7 +15,17 @@ import java.util.Set;
* 3. nondeterministic states * 3. nondeterministic states
*/ */
public class DFAVerifier { 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; DFA dfa;
StackLimitedNFAToDFAConverter converter; StackLimitedNFAToDFAConverter converter;
public DFAVerifier(DFA dfa, StackLimitedNFAToDFAConverter converter) { public DFAVerifier(DFA dfa, StackLimitedNFAToDFAConverter converter) {
@ -20,11 +33,128 @@ public class DFAVerifier {
this.converter = converter; this.converter = converter;
} }
public void analyze() {
}
public Set<Integer> getUnreachableAlts() { 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 * conflicting ctx predicts alts i and j. Return an Integer set
* of the alternative numbers that conflict. Two contexts conflict if * 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 * 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 * Use a hash table to record the lists of configs for each state
* as they are encountered. We need only consider states for which * 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 * alt must be different or must have different contexts to avoid a
* conflict. * conflict.
*/ */
public Set<Integer> getNonDeterministicAlts(DFAState d) { public Set<Integer> getAmbiguousAlts(DFAState d) {
//System.out.println("getNondetAlts for DFA state "+stateNumber); //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; // If only 1 NFA conf then no way it can be nondeterministic;
// save the overhead. There are many o-a->o NFA transitions // 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 // 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()) { for (List<NFAConfig> configsForState : stateToConfigListMap.values()) {
if ( configsForState.size()>1 ) { if ( configsForState.size()>1 ) {
int predictedAlt = Resolver.getUniqueAlt(configsForState, false); int predictedAlt = Resolver.getUniqueAlt(configsForState, false);
@ -65,13 +67,19 @@ public class Resolver {
stateToConfigListMap.put(configsForState.get(0).state.stateNumber, null); stateToConfigListMap.put(configsForState.get(0).state.stateNumber, null);
} }
else { else {
thisStateHasPotentialProblem = true; //thisStateHasPotentialProblem = true;
deterministic = false;
} }
} }
} }
// a fast check for potential issues; most states have none // 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 // we have a potential problem, so now go through config lists again
// looking for different alts (only states with potential issues // looking for different alts (only states with potential issues
@ -107,35 +115,34 @@ public class Resolver {
ctxConflict = s.context.conflictsWith(t.context); ctxConflict = s.context.conflictsWith(t.context);
} }
if ( altConflict && ctxConflict ) { if ( altConflict && ctxConflict ) {
nondeterministicAlts.add(s.alt); ambiguousAlts.add(s.alt);
nondeterministicAlts.add(t.alt); ambiguousAlts.add(t.alt);
} }
} }
} }
} }
if ( nondeterministicAlts.size()==0 ) return null; if ( ambiguousAlts.size()==0 ) return null;
return nondeterministicAlts; return ambiguousAlts;
} }
public void resolveNonDeterminisms(DFAState d) { public void resolveAmbiguities(DFAState d) {
if ( StackLimitedNFAToDFAConverter.debug ) { if ( StackLimitedNFAToDFAConverter.debug ) {
System.out.println("resolveNonDeterminisms "+d.toString()); System.out.println("resolveNonDeterminisms "+d.toString());
} }
Set<Integer> nondeterministicAlts = getNonDeterministicAlts(d); Set<Integer> ambiguousAlts = getAmbiguousAlts(d);
if ( StackLimitedNFAToDFAConverter.debug && nondeterministicAlts!=null ) { if ( StackLimitedNFAToDFAConverter.debug && ambiguousAlts!=null ) {
System.out.println("nondet alts="+nondeterministicAlts); System.out.println("ambig alts="+ambiguousAlts);
} }
// if no problems return // if no problems return
if ( nondeterministicAlts==null ) return; if ( ambiguousAlts==null ) return;
// reportNondeterminism(d, nondeterministicAlts); converter.ambiguousStates.add(d);
converter.nondeterministicStates.add(d);
// ATTEMPT TO RESOLVE WITH SEMANTIC PREDICATES // ATTEMPT TO RESOLVE WITH SEMANTIC PREDICATES
boolean resolved = boolean resolved =
semResolver.tryToResolveWithSemanticPredicates(d, nondeterministicAlts); semResolver.tryToResolveWithSemanticPredicates(d, ambiguousAlts);
if ( resolved ) { if ( resolved ) {
if ( StackLimitedNFAToDFAConverter.debug ) { if ( StackLimitedNFAToDFAConverter.debug ) {
System.out.println("resolved DFA state "+d.stateNumber+" with pred"); 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 // 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 /** 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 * There may be many alts among the configurations but only turn off
* the ones with problems (other than the min alt of course). * 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. * associated with the minimum alt.
* *
* Return the min alt found. * Return the min alt found.
*/ */
int resolveByPickingMinAlt(DFAState d, Set<Integer> nondeterministicAlts) { int resolveByPickingMinAlt(DFAState d, Set<Integer> alts) {
int min = Integer.MAX_VALUE; int min = Integer.MAX_VALUE;
if ( nondeterministicAlts!=null ) { if ( alts !=null ) {
min = getMinAlt(nondeterministicAlts); min = getMinAlt(alts);
} }
else { else {
min = d.getMinAlt(); min = d.getMinAlt();
} }
turnOffOtherAlts(d, min, nondeterministicAlts); turnOffOtherAlts(d, min, alts);
return min; return min;
} }
/** turn off all states associated with alts other than the good one /** 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(); int numConfigs = d.nfaConfigs.size();
for (int i = 0; i < numConfigs; i++) { for (int i = 0; i < numConfigs; i++) {
NFAConfig configuration = d.nfaConfigs.get(i); NFAConfig configuration = d.nfaConfigs.get(i);
if ( configuration.alt!=min ) { if ( configuration.alt!=min ) {
if ( nondeterministicAlts==null || if ( alts==null ||
nondeterministicAlts.contains(configuration.alt) ) alts.contains(configuration.alt) )
{ {
configuration.resolved = true; 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; int min = Integer.MAX_VALUE;
for (Integer altI : nondeterministicAlts) { for (Integer altI : alts) {
int alt = altI.intValue(); int alt = altI.intValue();
if ( alt < min ) min = alt; if ( alt < min ) min = alt;
} }

View File

@ -33,25 +33,28 @@ public class StackLimitedNFAToDFAConverter {
* lead to this situation (assuming no semantic predicates can resolve * lead to this situation (assuming no semantic predicates can resolve
* the problem) and when for some reason, I cannot compute the lookahead * the problem) and when for some reason, I cannot compute the lookahead
* (which might arise from an error in the algorithm or from * (which might arise from an error in the algorithm or from
* left-recursion etc...). This list starts out with all alts contained * left-recursion etc...).
* and then in method doesStateReachAcceptState() I remove the alts I
* know to be uniquely predicted.
*/ */
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 * 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 * 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 * alternative's left edge. If the context is the same or conflicts,
* resolve the issue, but track info anyway. * then we have ambiguity. If the context is different, it's simply
* Note that from the DFA state, you can ask for * nondeterministic and we should keep looking for edges that will
* which alts are nondeterministic. * 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). */ /** The set of states w/o emanating edges (and w/o resolving sem preds). */
public Set<DFAState> danglingStates = new HashSet<DFAState>(); 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 /** Was a syntactic ambiguity resolved with predicates? Any DFA
* state that predicts more than one alternative, must be resolved * state that predicts more than one alternative, must be resolved
* with predicates or it should be reported to the user. * with predicates or it should be reported to the user.
@ -66,6 +69,12 @@ public class StackLimitedNFAToDFAConverter {
Set<DFAState> recursionOverflowStates = new HashSet<DFAState>(); 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 /** Used to prevent the closure operation from looping to itself and
* hence looping forever. Sensitive to the NFA state, the alt, and * hence looping forever. Sensitive to the NFA state, the alt, and
* the stack context. * the stack context.
@ -74,6 +83,8 @@ public class StackLimitedNFAToDFAConverter {
Resolver resolver; Resolver resolver;
DFAVerifier verifier;
public static boolean debug = false; public static boolean debug = false;
public StackLimitedNFAToDFAConverter(Grammar g, DecisionState nfaStartState) { public StackLimitedNFAToDFAConverter(Grammar g, DecisionState nfaStartState) {
@ -82,10 +93,7 @@ public class StackLimitedNFAToDFAConverter {
dfa = new DFA(g, nfaStartState); dfa = new DFA(g, nfaStartState);
dfa.converter = this; dfa.converter = this;
resolver = new Resolver(this); resolver = new Resolver(this);
unreachableAlts = new ArrayList<Integer>(); verifier = new DFAVerifier(dfa, this);
for (int i = 1; i <= dfa.nAlts; i++) {
unreachableAlts.add(i);
}
} }
public DFA createDFA() { public DFA createDFA() {
@ -101,6 +109,9 @@ public class StackLimitedNFAToDFAConverter {
work.remove(0); // we're done with this DFA state work.remove(0); // we're done with this DFA state
} }
unreachableAlts = verifier.getUnreachableAlts();
//deadStates = verifier.getDeadStates();
return dfa; 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 */ /** Add t if not in DFA yet, resolving nondet's and then make d-label->t */
void addTransition(DFAState d, IntervalSet label, DFAState 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 if ( existing != null ) { // seen before; point at old one
d.addTransition(new Edge(existing, label)); d.addTransition(new Edge(existing, label));
return; return;
@ -152,7 +163,7 @@ public class StackLimitedNFAToDFAConverter {
// resolve any syntactic conflicts by choosing a single alt or // resolve any syntactic conflicts by choosing a single alt or
// by using semantic predicates if present. // by using semantic predicates if present.
resolver.resolveNonDeterminisms(t); resolver.resolveAmbiguities(t);
// If deterministic, don't add this state; it's an accept state // If deterministic, don't add this state; it's an accept state
// Just return as a valid DFA 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 * Not used during fixed k lookahead as it's a waste to fill it with
* a dup of states array. * 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 /** 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 * 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). */ /** Add a new DFA state to this DFA (doesn't check if already present). */
public void addState(DFAState d) { public void addState(DFAState d) {
uniqueStates.put(d,d); states.put(d,d);
d.stateNumber = stateCounter++; d.stateNumber = stateCounter++;
} }
public void defineAcceptState(int alt, DFAState acceptState) { public void defineAcceptState(int alt, DFAState acceptState) {
if ( uniqueStates.get(acceptState)==null ) addState(acceptState); if ( states.get(acceptState)==null ) addState(acceptState);
altToAcceptState[alt] = acceptState; altToAcceptState[alt] = acceptState;
} }
@ -80,7 +80,7 @@ public class DFA {
public boolean isDeterministic() { public boolean isDeterministic() {
if ( converter.danglingStates.size()==0 && if ( converter.danglingStates.size()==0 &&
converter.nondeterministicStates.size()==0 && converter.ambiguousStates.size()==0 &&
converter.unreachableAlts.size()==0 ) converter.unreachableAlts.size()==0 )
{ {
return true; return true;

View File

@ -50,7 +50,7 @@ public class DFAState {
public DFA dfa; public DFA dfa;
/** Track the transitions emanating from this DFA state. */ /** Track the transitions emanating from this DFA state. */
protected List<Edge> edges = public List<Edge> edges =
new ArrayList<Edge>(INITIAL_NUM_TRANSITIONS); new ArrayList<Edge>(INITIAL_NUM_TRANSITIONS);
/** The set of NFA configurations (state,alt,context) for this DFA state */ /** 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? */ /** NFAState created for which rule? */
public Rule 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 @Override
public int hashCode() { public int hashCode() {
return super.hashCode(); return super.hashCode();
@ -28,14 +36,6 @@ public class NFAState {
return String.valueOf(stateNumber); 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() { public int getNumberOfTransitions() {
return 0; return 0;
} }

View File

@ -20,12 +20,6 @@ public class DOTGenerator {
/** Library of output templates; use <attrname> format */ /** Library of output templates; use <attrname> format */
public static STGroup stlib = new STGroupDir("org/antlr/v4/tool/templates/dot"); 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; protected Grammar grammar;
/** This aspect is associated with a grammar */ /** This aspect is associated with a grammar */
@ -38,97 +32,22 @@ public class DOTGenerator {
* from startState will be included. * from startState will be included.
*/ */
public String getDOT(NFAState startState) { public String getDOT(NFAState startState) {
if ( startState==null ) { if ( startState==null ) return null;
return null;
}
// The output DOT graph for visualization // The output DOT graph for visualization
ST dot = null; ST dot = null;
markedStates = new HashSet<Integer>(); Set<NFAState> markedStates = new HashSet<NFAState>();
dot = stlib.getInstanceOf("nfa"); dot = stlib.getInstanceOf("nfa");
dot.add("startState", dot.add("startState", Utils.integer(startState.stateNumber));
Utils.integer(startState.stateNumber));
walkRuleNFACreatingDOT(dot, startState);
dot.add("rankdir", rankdir); dot.add("rankdir", rankdir);
return dot.render();
}
public String getDOT(DFAState startState) { List<NFAState> work = new LinkedList<NFAState>();
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();
}
/** Do a depth-first walk of the state machine graph and work.add(startState);
* fill a DOT description template. Keep filling the while ( work.size()>0 ) {
* states and edges attributes. NFAState s = work.get(0);
*/ if ( markedStates.contains(s) ) { work.remove(0); continue; }
protected void walkCreatingDFADOT(ST dot, markedStates.add(s);
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.
// first add this node // first add this node
ST stateST; ST stateST;
@ -139,11 +58,9 @@ public class DOTGenerator {
stateST = stlib.getInstanceOf("state"); stateST = stlib.getInstanceOf("state");
} }
stateST.add("name", getStateLabel(s)); stateST.add("name", getStateLabel(s));
dot.add("states", stateST);
if ( s instanceof RuleStopState ) { // don't go past end of rule node to the follow states
return; // 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 // special case: if decision point, then line up the alt start states
// unless it's an end of block // unless it's an end of block
@ -153,7 +70,7 @@ public class DOTGenerator {
ST rankST = stlib.getInstanceOf("decision-rank"); ST rankST = stlib.getInstanceOf("decision-rank");
NFAState alt = (NFAState)s; NFAState alt = (NFAState)s;
while ( alt!=null ) { while ( alt!=null ) {
rankST.add("states", getStateLabel(alt)); rankST.add("states", alt.stateNumber);
if ( alt.transition(1) !=null ) { if ( alt.transition(1) !=null ) {
alt = (NFAState)alt.transition(1).target; alt = (NFAState)alt.transition(1).target;
} }
@ -179,11 +96,11 @@ public class DOTGenerator {
else { else {
edgeST.add("label", "<"+rr.rule.name+">"); edgeST.add("label", "<"+rr.rule.name+">");
} }
edgeST.add("src", getStateLabel(s)); edgeST.add("src", s.stateNumber);
edgeST.add("target", getStateLabel(rr.followState)); edgeST.add("target", rr.followState.stateNumber);
edgeST.add("arrowhead", arrowhead); edgeST.add("arrowhead", arrowhead);
dot.add("edges", edgeST); dot.add("edges", edgeST);
walkRuleNFACreatingDOT(dot, rr.followState); work.add(rr.followState);
continue; continue;
} }
if ( edge instanceof ActionTransition ) { if ( edge instanceof ActionTransition ) {
@ -199,14 +116,183 @@ public class DOTGenerator {
edgeST = stlib.getInstanceOf("edge"); edgeST = stlib.getInstanceOf("edge");
} }
edgeST.add("label", getEdgeLabel(edge.toString(grammar))); edgeST.add("label", getEdgeLabel(edge.toString(grammar)));
edgeST.add("src", getStateLabel(s)); edgeST.add("src", s.stateNumber);
edgeST.add("target", getStateLabel(edge.target)); edgeST.add("target", edge.target.stateNumber);
edgeST.add("arrowhead", arrowhead); edgeST.add("arrowhead", arrowhead);
dot.add("edges", edgeST); 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; /** Fix edge strings so they print out in DOT properly;
* generate any gated predicates on edge too. * generate any gated predicates on edge too.
*/ */
@ -222,14 +308,13 @@ public class DOTGenerator {
if ( s==null ) return "null"; if ( s==null ) return "null";
String stateLabel = String.valueOf(s.stateNumber); String stateLabel = String.valueOf(s.stateNumber);
if ( s instanceof DecisionState ) { 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) { protected String getStateLabel(DFAState s) {
if ( s==null ) return "null"; if ( s==null ) return "null";
String stateLabel = String.valueOf(s.stateNumber);
StringBuffer buf = new StringBuffer(250); StringBuffer buf = new StringBuffer(250);
buf.append('s'); buf.append('s');
buf.append(s.stateNumber); buf.append(s.stateNumber);
@ -275,10 +360,10 @@ public class DOTGenerator {
} }
} }
} }
stateLabel = buf.toString(); String stateLabel = buf.toString();
if ( s.isAcceptState ) { if ( s.isAcceptState ) {
stateLabel = stateLabel+"=>"+s.getUniquelyPredictedAlt(); stateLabel = stateLabel+"=>"+s.getUniquelyPredictedAlt();
} }
return '"'+stateLabel+'"'; return stateLabel;
} }
} }