/*
* JFLAP - Formal Languages and Automata Package
*
*
* Susan H. Rodger
* Computer Science Department
* Duke University
* August 27, 2009
* Copyright (c) 2002-2009
* All rights reserved.
* JFLAP is open source software. Please see the LICENSE for terms.
*
*/
package automata.fsa;
import gui.environment.Universe;
import java.util.ArrayList;
import regular.Discretizer;
import automata.Automaton;
import automata.State;
import automata.StatePlacer;
import automata.Transition;
/**
* The fsa to regular expression converter can be used to convert a finite state
* automaton to its equivalent regular expression. In order to perform this
* conversion, you need to convert the finite state automaton into a "simple"
* automaton (i.e. an automaton with a single final state and a different single
* initial state and exactly one transition between all combinations of states)
* by calling convertToSimpleAutomaton. Or you can do this conversion step by
* step, by first calling getSingleFinalState to give your automaton a unique,
* single final state. Then, you would need to check all combinations of pairs
* of states in your automaton to see if they have a single transition between
* them (remember, the simple automaton needs exactly one transition between all
* pairs of states). If there are 0 transitions between any two states, you can
* call addTransitionOnEmptySet to create a transition on the empty set between
* the two states. Or if there are more than one transitions between any two
* states, you can call combineToSingleTransition to combine all those
* transitions to a single transition between the two states. Then you can
* convert this automaton immediately to its equivalent regular expression by
* calling convertToRegularExpression, or you can perform this conversion step
* by step. First you need to remove all states other than the final and initial
* states. You do this by calling getTransitionsForRemoveState and then
* removeState for each state. Once this is completed, you will have a
* generalized transition graph (with only two states--an initial and final
* state). At this point you can get the regular expression by calling
* getExpressionFromGTG, or you can do that work yourself by calling getII,
* getIJ, getJJ, and getJI to get the expressions on the four arcs in your
* two-state generalized transition graph, and then calling getFinalExpression.
*
* @author Ryan Cavalcante
*/
public class FSAToRegularExpressionConverter {
/**
* Creates an instance of FSAToRegularExpressionConverter
.
*/
private FSAToRegularExpressionConverter() {
}
/**
* Returns true if automaton
can be converted to a regular
* expression (i.e. it has a unique initial and final state and it is a
* finite state automaton, and the initial state is not the final state).
*
* @param automaton
* the automaton to convert
* @return true if automaton
can be converted to a regular
* expression.
*/
public static boolean isConvertable(Automaton automaton) {
if (!(automaton instanceof FiniteStateAutomaton))
return false;
State[] finalStates = automaton.getFinalStates();
if (finalStates.length != 1) {
return false;
}
State initialState = automaton.getInitialState();
if (finalStates[0] == initialState) {
return false;
}
return true;
}
/**
* Returns true if there are more removable states in automaton
.
*
* @param automaton
* the automaton
* @return true if there are more removable states in automaton
.
*/
public static boolean areRemovableStates(Automaton automaton) {
State[] states = automaton.getStates();
for (int k = 0; k < states.length; k++) {
if (isRemovable(states[k], automaton))
return true;
}
return false;
}
/**
* Returns true if state
is a removable state (i.e. it is not
* the unique initial or final state).
*
* @param state
* the state to remove.
* @param automaton
* the automaton.
* @return true if state
is a removable state
*/
public static boolean isRemovable(State state, Automaton automaton) {
State[] finalStates = automaton.getFinalStates();
State finalState = finalStates[0];
State initialState = automaton.getInitialState();
if (state == finalState || state == initialState)
return false;
return true;
}
/**
* Returns a Transition object that represents the transition between the
* states with ID's p
and q
, with expression
* as the transition label.
*
* @param p
* the ID of the from state.
* @param q
* the ID of the to state.
* @param expression
* the expression
* @param automaton
* the automaton
* @return a Transition object that represents the transition between the
* states with ID's p
and q
, with
* expression
as the transition label.
*/
public static Transition getTransitionForExpression(int p, int q,
String expression, Automaton automaton) {
State fromState = automaton.getStateWithID(p);
State toState = automaton.getStateWithID(q);
Transition transition = new FSATransition(fromState, toState,
expression);
return transition;
}
/**
* Returns the expression on the transition between fromState
* and toState
in automaton
.
*
* @param fromState
* the from state
* @param toState
* the to state
* @param automaton
* the automaton
* @return the expression on the transition between fromState
* and toState
in automaton
.
*/
public static String getExpressionBetweenStates(State fromState, State toState,
Automaton automaton) {
Transition[] transitions = automaton.getTransitionsFromStateToState(
fromState, toState);
FSATransition trans = (FSATransition) transitions[0];
return trans.getLabel();
}
/**
* Returns the expression obtained from evaluating the following equation:
* r(pq) = r(pq) + r(pk)r(kk)*r(kq), where p, q, and k represent the IDs of
* states in automaton
.
*
* @param p
* the from state
* @param q
* the to state
* @param k
* the state being removed.
* @param automaton
* the automaton.
* @return the expression obtained from evaluating the following equation:
* r(pq) = r(pq) + r(pk)r(kk)*r(kq), where p, q, and k represent the
* IDs of states in automaton
.
*/
public static String getExpression(int p, int q, int k, Automaton automaton) {
State fromState = automaton.getStateWithID(p);
State toState = automaton.getStateWithID(q);
State removeState = automaton.getStateWithID(k);
String pq = getExpressionBetweenStates(fromState, toState, automaton);
String pk = getExpressionBetweenStates(fromState, removeState,
automaton);
String kk = getExpressionBetweenStates(removeState, removeState,
automaton);
String kq = getExpressionBetweenStates(removeState, toState, automaton);
String temp1 = star(kk);
String temp2 = concatenate(pk, temp1);
String temp3 = concatenate(temp2, kq);
String label = or(pq, temp3);
return label;
}
/**
* Returns the expression that represents r1
concatenated
* with r2
. (essentialy just the two strings concatenated).
*
* @param r1
* the first part of the expression.
* @param r2
* the second part of the expression.
* @return the expression that represents r1
concatenated
* with r2
. (essentialy just the two strings
* concatenated).
*/
public static String concatenate(String r1, String r2) {
if (r1.equals(EMPTY) || r2.equals(EMPTY))
return EMPTY;
else if (r1.equals(LAMBDA))
return r2;
else if (r2.equals(LAMBDA))
return r1;
if (Discretizer.or(r1).length > 1)
r1 = addParen(r1);
if (Discretizer.or(r2).length > 1)
r2 = addParen(r2);
return r1 + r2;
}
/**
* Returns the expression that represents r1
kleene-starred.
*
* @param r1
* the expression being kleene-starred.
* @return the expression that represents r1
kleene-starred.
*/
public static String star(String r1) {
if (r1.equals(EMPTY) || r1.equals(LAMBDA))
return LAMBDA;
if (Discretizer.or(r1).length > 1 || Discretizer.cat(r1).length > 1) {
r1 = addParen(r1);
} else {
if (r1.endsWith(KLEENE_STAR))
return r1;
}
return r1 + KLEENE_STAR;
}
/**
* Returns the string that represents r1
or'ed with r2
.
*
* @param r1
* the first expression
* @param r2
* the second expression
* @return the string that represents r1
or'ed with r2
.
*/
public static String or(String r1, String r2) {
if (r1.equals(EMPTY))
return r2;
if (r2.equals(EMPTY))
return r1;
if (r1.equals(LAMBDA) && r2.equals(LAMBDA))
return LAMBDA;
if (r1.equals(LAMBDA))
r1 = LAMBDA_DISPLAY;
if (r2.equals(LAMBDA))
r2 = LAMBDA_DISPLAY;
// if(needsParens(r1)) r1 = addParen(r1);
// if(needsParens(r2)) r2 = addParen(r2);
return r1 + OR + r2;
}
/**
* Completely reconstructs automaton
, removing all
* transitions and state
and adding all transitions in transitions
.
*
* @param state
* the state to remove.
* @param transitions
* the transitions returned for removing state
.
* @param automaton
* the automaton.
*/
public static void removeState(State state, Transition[] transitions,
Automaton automaton) {
Transition[] oldTransitions = automaton.getTransitions();
for (int k = 0; k < oldTransitions.length; k++) {
automaton.removeTransition(oldTransitions[k]);
}
automaton.removeState(state);
for (int i = 0; i < transitions.length; i++) {
automaton.addTransition(transitions[i]);
}
}
/**
* Returns a list of all transitions for automaton
created by
* removing state
.
*
* @param state
* the state to remove.
* @param automaton
* the automaton.
* @return a list of all transitions for automaton
created by
* removing state
.
*/
public static Transition[] getTransitionsForRemoveState(State state,
Automaton automaton) {
if (!isRemovable(state, automaton))
return null;
ArrayList list = new ArrayList();
int k = state.getID();
State[] states = automaton.getStates();
for (int i = 0; i < states.length; i++) {
int p = states[i].getID();
if (p != k) {
for (int j = 0; j < states.length; j++) {
int q = states[j].getID();
if (q != k) {
String exp = getExpression(p, q, k, automaton);
list.add(getTransitionForExpression(p, q, exp,
automaton));
}
}
}
}
return (Transition[]) list.toArray(new Transition[0]);
}
/**
* Adds a new transition to automaton
between fromState
* and toState on the symbol for the empty set.
*
* @param fromState
* the from state for the transition
* @param toState
* the to state for the transition
* @param automaton
* the automaton.
* @return the FSATransition
that was created
*/
public static FSATransition addTransitionOnEmptySet(State fromState,
State toState, Automaton automaton) {
FSATransition t = new FSATransition(fromState, toState, EMPTY);
automaton.addTransition(t);
return t;
}
/**
* Removes all transitions in transitions
from automaton
,
* replacing them with a single transition in automaton
* between fromState
and toState
labeled with
* a regular expression that represents the labels of all the removed
* transitions Or'ed together (e.g. a + (b*c) + (d+e)).
*
* @param fromState
* the from state for transitions
and for the
* newly created transition.
* @param toState
* the to state for transitions
and for the newly
* created transition.
* @param transitions
* the transitions being removed and combined into a single
* transition
* @param automaton
* the automaton
* @return the transition that replaced all of these
*/
public static FSATransition combineToSingleTransition(State fromState,
State toState, Transition[] transitions, Automaton automaton) {
String label = ((FSATransition) transitions[0]).getDescription();
automaton.removeTransition(transitions[0]);
for (int i = 1; i < transitions.length; i++) {
label = or(label, ((FSATransition) transitions[i]).getDescription());
automaton.removeTransition(transitions[i]);
}
FSATransition t = new FSATransition(fromState, toState, label);
automaton.addTransition(t);
return t;
}
/**
* Makes all final states in automaton
non-final, adding
* transitions from these states to a newly created final state on lambda.
*
* @param automaton
* the automaton
*/
public static void getSingleFinalState(Automaton automaton) {
StatePlacer sp = new StatePlacer();
State finalState = automaton
.createState(sp.getPointForState(automaton));
State[] finalStates = automaton.getFinalStates();
for (int k = 0; k < finalStates.length; k++) {
State state = finalStates[k];
automaton
.addTransition(new FSATransition(state, finalState, LAMBDA));
automaton.removeFinalState(state);
}
automaton.addFinalState(finalState);
}
/**
* Converts automaton
to an equivalent automaton with a
* single transition between all combinations of states. (if there are
* currently more than one transition between two states, it combines them
* into a single transition by or'ing the labels of all the transitions. If
* there is no transition between two states, it creates a transition and
* labels it with the empty set character (EMPTY).
*
* @param automaton
* the automaton.
*/
public static void convertToSimpleAutomaton(Automaton automaton) {
if (!isConvertable(automaton))
getSingleFinalState(automaton);
State[] states = automaton.getStates();
for (int k = 0; k < states.length; k++) {
for (int j = 0; j < states.length; j++) {
Transition[] transitions = automaton
.getTransitionsFromStateToState(states[k], states[j]);
if (transitions.length == 0) {
addTransitionOnEmptySet(states[k], states[j], automaton);
}
if (transitions.length > 1) {
combineToSingleTransition(states[k], states[j],
transitions, automaton);
}
}
}
}
/**
* Converts automaton
into a generalized transition graph
* with only two states, a unique initial state, and a unique final state.
*
* @param automaton
* the automaton.
*/
public static void convertToGTG(Automaton automaton) {
State[] finalStates = automaton.getFinalStates();
State finalState = finalStates[0];
State initialState = automaton.getInitialState();
State[] states = automaton.getStates();
for (int k = 0; k < states.length; k++) {
State state = states[k];
if (state != finalState && state != initialState) {
Transition[] transitions = getTransitionsForRemoveState(state,
automaton);
removeState(state, transitions, automaton);
}
}
}
/**
* Returns true if word
is one character long and it is a
* letter.
*
* @param word
* the word
* @return true if word
is one character long and it is a
* letter.
*/
public static boolean isSingleCharacter(String word) {
if (word.length() != 1)
return false;
char ch = word.charAt(0);
return Character.isLetter(ch);
}
/**
* Returns true if word
needs parens. (i.e. it is an '+' (OR)
* expression)
*
* @param word
* the word.
* @return true if word
needs parens. (i.e. it is an '+' (OR)
* expression)
*/
public static boolean needsParens(String word) {
for (int k = 0; k < word.length(); k++) {
char ch = word.charAt(k);
if (ch == '+')
return true;
}
return false;
}
/**
* Returns a string of word
surrounded by parentheses. i.e. (),
* unless it is unnecessary.
*
* @param word
* the word.
* @return a string of word
surrounded by parentheses.
*/
public static String addParen(String word) {
return LEFT_PAREN + word + RIGHT_PAREN;
}
/**
* Returns a non-unicoded version of word
for debug purposes.
*
* @param word
* the expression to output
* @return a non-unicoded version of word
for debug purposes.
*/
public static String getExp(String word) {
if (word.equals(LAMBDA))
return "lambda";
else if (word.equals(EMPTY))
return "empty";
return word;
}
/**
* Returns the expression for the values of ii, ij, jj, and ji determined
* from the GTG with a unique initial and final state.
*
* @param ii
* the expression on the loop off the initial state
* @param ij
* the expression on the arc from the initial state to the final
* state.
* @param jj
* the expression on the loop off the final state.
* @param ji
* the expression on the arc from the final state to the initial
* state.
* @return the expression for the values of ii, ij, jj, and ji determined
* from the GTG with a unique initial and final state.
*/
public static String getFinalExpression(String ii, String ij, String jj, String ji) {
String temp = concatenate(star(ii), concatenate(ij, concatenate(
star(jj), ji)));
String temp2 = concatenate(star(ii), concatenate(ij, star(jj)));
// String expression =
// concatenate(star(concatenate(LEFT_PAREN,concatenate(temp,RIGHT_PAREN))),
// temp2);
String expression = concatenate(star(temp), temp2);
return expression;
}
/**
* Returns the expression on the loop off the initial state of automaton
.
*
* @param automaton
* a generalized transition graph with only two states, a unique
* initial and final state.
* @return the expression on the loop off the initial state of automaton
.
*/
public static String getII(Automaton automaton) {
State initialState = automaton.getInitialState();
return getExpressionBetweenStates(initialState, initialState, automaton);
}
/**
* Returns the expression on the arc from the initial state to the final
* state of automaton
.
*
* @param automaton
* a generalized transition graph with only two states, a unique
* initial and final state.
* @return the expression on the arc from the initial state to the final
* state of automaton
.
*/
public static String getIJ(Automaton automaton) {
State initialState = automaton.getInitialState();
State[] finalStates = automaton.getFinalStates();
State finalState = finalStates[0];
return getExpressionBetweenStates(initialState, finalState, automaton);
}
/**
* Returns the expression on the loop off the final state of automaton
*
* @param automaton
* a generalized transition graph with only two states, a unique
* initial and final state.
* @return the expression on the loop off the final state of automaton
*/
public static String getJJ(Automaton automaton) {
State[] finalStates = automaton.getFinalStates();
State finalState = finalStates[0];
return getExpressionBetweenStates(finalState, finalState, automaton);
}
/**
* Returns the expression on the arc from the final state to the initial
* state of automaton
*
* @param automaton
* a generalized transition graph with only two states, a unique
* initial and final state.
* @return the expression on the arc from the final state to the initial
* state of automaton
*/
public static String getJI(Automaton automaton) {
State initialState = automaton.getInitialState();
State[] finalStates = automaton.getFinalStates();
State finalState = finalStates[0];
return getExpressionBetweenStates(finalState, initialState, automaton);
}
/**
* Returns the expression for the generalized transition graph automaton
* with two states, a unique initial and unique final state. Evaluates to
* the expression r = (r(ii)*r(ij)r(jj)*r(ji))*r(ii)*r(ij)r(jj)*. where
* r(ij) represents the expression on the transition between state i (the
* initial state) and state j (the final state)
*
* @param automaton
* the generalized transition graph with two states (a unique
* initial and final state).
* @return the expression for the generalized transition graph automaton
* with two states, a unique initial and unique final state
*/
public static String getExpressionFromGTG(Automaton automaton) {
String ii = getII(automaton);
String ij = getIJ(automaton);
String jj = getJJ(automaton);
String ji = getJI(automaton);
return getFinalExpression(ii, ij, jj, ji);
}
/**
* Returns the regular expression that represents automaton
.
*
* @param automaton
* the automaton
* @return the regular expression that represents automaton
.
*/
public static String convertToRegularExpression(Automaton automaton) {
if (!isConvertable(automaton))
return null;
convertToGTG(automaton);
return getExpressionFromGTG(automaton);
}
/* the string for the empty set. */
public static final String EMPTY = "\u00F8";
/* the string for lambda. */
public static final String LAMBDA_DISPLAY = Universe.curProfile.getEmptyString();
public static final String LAMBDA = "";
/* the string for the kleene star. */
public static final String KLEENE_STAR = "*";
/* the string for the or symbol. */
public static final String OR = "+";
/** right paren. */
public static final String RIGHT_PAREN = ")";
/** left paren. */
public static final String LEFT_PAREN = "(";
}