package project.phase2.ll1parsergenerator.dfastuff;
import java.util.*;
/**
* A specific, Deterministic, type of Finite Automaton. This specific example assumes Characters as the transition values.
*/
public class DFA extends TableDrivenFiniteAutomaton<String>
{
/**
* A current match, for parsing through data iteratively.
*/
private TokenMatch mCurrMatch;
/**
* Used to reset iterative matching.
*/
public void reset()
{
mCurrMatch = null;
}
/**
* Used to iteratively test input for matching.
*
* @param input the input token.
* @return the match descriptor.
*/
public TokenMatch test(String input)
{
if(mCurrMatch == null)
mCurrMatch = new TokenMatch(this.getStartState());
if(mCurrMatch.isRejected())
return mCurrMatch;
Integer state = mCurrMatch.mState;
Transition<String, Integer> t = this.getTransition(input, state);
if(t == null || t.getDestinations().length < 1)
mCurrMatch = new TokenMatch(-1);
else
mCurrMatch = new TokenMatch(t.getDestinations()[0]);
return mCurrMatch;
}
/*
* (non-Javadoc)
* @see dfabuilder.TableDrivenFiniteAutomaton#addTransition(dfabuilder.Transition)
*/
public boolean addTransition(Transition<String, Integer> trans)
{
if(trans.getDestinations().length != 1 || trans.getValue() == EMPTY_TRANSITION)
return false;
else
{
if(getTransition(trans.getValue(), trans.getStart()) != null)
{
return false;
}
else
{
return super.addTransition(trans);
}
}
}
/**
* Creates a minimum DFA from the current DFA.
*
* @return a DFA that has been minimized.
*/
public DFA minimize()
{
// The NFA-DFA conversion on this DFA is to remove unreachable states.
DFA retDFA = new DFA(), startDFA = DFA.fromNFA(this);
int numStates = startDFA.getStates().size();
if(numStates < 1)
return retDFA;
Set<Integer> goalStates = startDFA.getGoalStates();
boolean[][] diff = new boolean[numStates][];
for(int i = 0; i < numStates; i++)
{
diff[i] = new boolean[i];
}
// Initialize to final states != nonfinal.
for(int j = 0; j < diff.length; j++)
{
for(int i = 0; i < diff[j].length; i++)
{
diff[j][i] = (goalStates.contains(j) != goalStates.contains(i));
if(!diff[j][i] && (goalStates.contains(j) && goalStates.contains(i)))
{
String sI = startDFA.getGoalLabel(i), sJ = startDFA.getGoalLabel(j);
diff[j][i] = (sI == null) ? (sJ != null) : (!sI.equals(sJ));
}
}
}
// Create our differences matrix.
// This builds a table of the kind we were shown in class.
boolean changed = true;
while(changed)
{
changed = false;
for(int j = 0; j < diff.length; j++)
{
for(int i = 0; i < diff[j].length; i++)
{
if(diff[j][i])
continue;
Set<String> transVals = new HashSet<String>();
transVals.addAll(startDFA.getTransitionValues(j));
int transSize = transVals.size();
transVals.addAll(startDFA.getTransitionValues(i));
// If adding the transition values for the second item
if(startDFA.getTransitionValues(i).size() != transSize || transVals.size() != transSize)
{
diff[j][i] = true;
changed = true;
continue;
}
for(String tVal : transVals)
{
Transition<String, Integer> t1 = startDFA.getTransition(tVal, i), t2 = startDFA.getTransition(tVal, j);
// We are a DFA, so we should only have 1 destination for any transition.
int dest1 = Math.max(t1.getDestinations()[0], t2.getDestinations()[0]), dest2 = Math.min(t1.getDestinations()[0], t2.getDestinations()[0]);
if(dest1 != dest2 && diff[dest1][dest2])
{
diff[j][i] = true;
changed = true;
break;
}
}
}
}
}
// Start building our new dfa representation.
Map<Integer, Set<Integer>> setMap = new HashMap<Integer, Set<Integer>>();
Map<Set<Integer>, Integer> stateMap = new HashMap<Set<Integer>, Integer>();
Map<Set<Integer>, Map<String, Set<Integer>>> transMap = new HashMap<Set<Integer>, Map<String, Set<Integer>>>();
// Find out what the aggregations our states are in are.
for(int j = diff.length - 1; j >= 0; j--)
{
if(setMap.containsKey(j))
continue;
Set<Integer> stateSet = new HashSet<Integer>();
stateSet.add(j);
for(int i = 0; i < diff[j].length; i++)
{
if(!diff[j][i])
stateSet.add(i);
}
for(Integer state : stateSet)
{
setMap.put(state, stateSet);
}
}
// Remove Dead States
Set<Integer> deadStates = new HashSet<Integer>();
for(Map.Entry<Integer, Set<Integer>> stateSet : setMap.entrySet())
{
Integer key = stateSet.getKey();
if(goalStates.contains(key))
continue;
boolean dead = true;
for(String s : startDFA.getTransitionValues(key))
{
Integer dest = startDFA.getTransition(s, key).getDestinations()[0];
if(!setMap.get(dest).equals(stateSet.getValue()))
{
dead = false;
break;
}
}
if(dead)
{
deadStates.add(key);
}
}
// Add all of the appropriate states and create the transition map.
for(Map.Entry<Integer , Set<Integer>> stateSet : setMap.entrySet())
{
if(!stateMap.containsKey(stateSet.getValue()))
{
if(deadStates.contains(stateSet.getKey()))
{
stateMap.put(stateSet.getValue(), null);
}
else
{
stateMap.put(stateSet.getValue(), retDFA.createState());
HashMap<String, Set<Integer>> currTrans = new HashMap<String, Set<Integer>>();
for(String s : startDFA.getTransitionValues(stateSet.getKey()))
{
Integer dest = startDFA.getTransition(s, stateSet.getKey()).getDestinations()[0];
if(!deadStates.contains(dest))
currTrans.put(s, setMap.get(dest));
}
if(goalStates.contains(stateSet.getKey()))
retDFA.setGoalState(stateMap.get(stateSet.getValue()), startDFA.getGoalLabel(stateSet.getKey()));
if(startDFA.getStartState() == stateSet.getKey())
retDFA.setStartState(stateMap.get(stateSet.getValue()));
transMap.put(stateSet.getValue(), currTrans);
}
}
}
// Actually add the transitions.
for(Map.Entry<Set<Integer>, Map<String, Set<Integer>>> state : transMap.entrySet())
{
Integer stateNumber = stateMap.get(state.getKey());
for(Map.Entry<String, Set<Integer>> transition : state.getValue().entrySet())
{
Transition<String, Integer> trans = new Transition<String, Integer>(transition.getKey(), stateNumber, new Integer[]{stateMap.get(transition.getValue())});
retDFA.addTransition(trans);
}
}
// Now we must construct our new DFA from our old DFA and correctly merge states.
return retDFA;
}
/**
* Creates a DFA given an NFA.
*
* @param nfa the nfa to convert to a dfa.
* @return the dfa
*/
public static DFA fromNFA(TableDrivenFiniteAutomaton<String> nfa)
{
DFA retDFA = new DFA();
if(nfa.getStates().size() < 1)
return retDFA;
// Our maps for building the DFA and understanding what sets of NFA states map to what DFA states.
Map<Set<Integer>, Integer> stateMap = new HashMap<Set<Integer>, Integer>();
Map<Set<Integer>, Map<String, Set<Integer>>> transMap = new HashMap<Set<Integer>, Map<String, Set<Integer>>>();
// Our aggregated states.
Set<Integer> currStates = new HashSet<Integer>(), newStates;
currStates.add(nfa.getStartState());
currStates = epsilonClosure(nfa, currStates);
// Current transitions.
Set<String> currTrans;
Transition<String, Integer> trans;
// Open list.
LinkedList<Set<Integer>> open = new LinkedList<Set<Integer>>();
open.add(currStates);
// We will need to fully explore everything.
while(!open.isEmpty())
{
// Get our next testing state.
currStates = open.poll();
if(!stateMap.containsKey(currStates))
{
// Update our data to build the
stateMap.put(currStates, retDFA.createState());
transMap.put(currStates, new HashMap<String, Set<Integer>>());
// Get our transitions.
currTrans = new HashSet<String>();
for(Integer state : currStates)
{
for(String s : nfa.getTransitionValues(state))
{
if(s != nfa.EMPTY_TRANSITION)
currTrans.add(s);
}
}
// Add successor states to the open list if they have not already been visited.
for(String transVal : currTrans)
{
newStates = new HashSet<Integer>();
// Get our successors for this transition.
for(Integer state : currStates)
{
trans = nfa.getTransition(transVal, state);
if(trans != null)
newStates.addAll(Arrays.asList(trans.getDestinations()));
}
// Get the epsilon closure of the new successor, add the transition to the current state's transitions.
newStates = epsilonClosure(nfa, newStates);
transMap.get(currStates).put(transVal, newStates);
// If we have not already explored the node, add it to the open list.
if(!stateMap.containsKey(newStates))
open.add(newStates);
}
}
}
// Add our transitions to the DFA.
for(Map.Entry<Set<Integer>, Map<String, Set<Integer>>> state : transMap.entrySet())
{
Integer stateNumber = stateMap.get(state.getKey());
for(Map.Entry<String, Set<Integer>> transition : state.getValue().entrySet())
{
trans = new Transition<String, Integer>(transition.getKey(), stateNumber, new Integer[]{stateMap.get(transition.getValue())});
retDFA.addTransition(trans);
}
}
// Set our start state.
newStates = new HashSet<Integer>();
newStates.add(nfa.getStartState());
newStates = epsilonClosure(nfa, newStates);
retDFA.setStartState(stateMap.get(newStates));
// Set our goal states.
Set<Integer> goals = nfa.getGoalStates();
for(Map.Entry<Set<Integer>, Integer> dfaState : stateMap.entrySet())
{
boolean isGoal = false;
Set<String> labels = new HashSet<String>();
for(Integer nfaState : dfaState.getKey())
{
String currLabel = nfa.getGoalLabel(nfaState);
if(goals.contains(nfaState))
{
isGoal = true;
if(currLabel != null)
labels.add(currLabel);
}
}
if(isGoal)
{
String label = null;
if(labels.size() > 0)
{
String[] labelArr = labels.toArray(new String[0]);
Arrays.sort(labelArr);
label = "";
for(String s : labelArr)
label += (label.length() == 0)?(s):("+" + s);
}
retDFA.setGoalState(dfaState.getValue(), label);
}
}
return retDFA;
}
//
// PRIVATE METHODS
//
private static Set<Integer> epsilonClosure(TableDrivenFiniteAutomaton<String> fa, Set<Integer> states)
{
Set<Integer> interSet, newSet = new HashSet<Integer>(), retSet = new HashSet<Integer>();
Transition<String, Integer> t;
retSet.addAll(states);
newSet.addAll(states);
int oldSize = 0, newSize = retSet.size();
while((oldSize != newSize) && !newSet.isEmpty())
{
interSet = newSet;
newSet = new HashSet<Integer>();
for(Integer state : interSet)
{
t = fa.getTransition(fa.EMPTY_TRANSITION, state);
if(t != null)
newSet.addAll(Arrays.asList(t.getDestinations()));
}
// This rather than add all to prevent duplications and same states being expaned over and over.
interSet = new HashSet<Integer>();
for(Integer newS : newSet)
{
if(!retSet.contains(newS))
{
interSet.add(newS);
retSet.add(newS);
}
}
newSet = interSet;
oldSize = newSize;
newSize = retSet.size();
}
return retSet;
}
//
// TESTING
//
public static void main(String[] args)
{
NFA n = new NFA();
n.createState();
n.setStartState(0);
n.createState();
n.addTransition(new Transition<String, Integer>("0", 0, new Integer[]{1}));
n.createState();
n.setGoalState(1);
n.addTransition(new Transition<String, Integer>("1", 0, new Integer[]{2}));
n.addTransition(new Transition<String, Integer>("0", 2, new Integer[]{2}));
n.createState();
n.addTransition(new Transition<String, Integer>("1", 2, new Integer[]{3}));
n.addTransition(new Transition<String, Integer>("0", 3, new Integer[]{3}));
n.addTransition(new Transition<String, Integer>("1", 3, new Integer[]{2}));
System.out.println("NFA");
System.out.println(n);
System.out.println();
DFA dfa = DFA.fromNFA(n);
System.out.println("DFA");
System.out.println(dfa);
System.out.println();
System.out.println("Minimized");
System.out.println(dfa.minimize());
}
//
// INNER CLASS
//
public class TokenMatch
{
//
// CLASS/INSTANCE DATA
//
/**
* The current DFA state.
*/
private int mState;
//
// CTOR
//
public TokenMatch(int dfaState)
{
mState = dfaState;
}
//
// PUBLIC METHODS
//
/**
* Returns whether or not the token was accepted.
*
* @return whether or not the token was accepted.
*/
public boolean isAccepted()
{
return getGoalStates().contains(mState);
}
/**
* Returns whether or not the token was rejected.
*
* @return whether or not the token was rejected.
*/
public boolean isRejected()
{
return !getStates().contains(mState);
}
/**
* Returns the label that the token has been accepted with (if it has one).
*
* @return the label that the token has been accepted with (if it has one).
*/
public String getLabel()
{
return getGoalLabel(mState);
}
}
}