/* * 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.pda; import grammar.Production; import grammar.cfg.ContextFreeGrammar; import gui.grammar.GrammarTableModel; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Stack; import automata.Automaton; import automata.State; import automata.Transition; /** * The PDA to context free grammar converter can be used to convert a pushdown * automaton into an equivalent context free grammar. The pda and grammar will * be equivalent in that they will accept exactly the same language. Before * using the converter, except for the first time, you must call * initializeConverter to prepare the converter for the conversion. This will * reset all data, maps, etc. used during the last conversion done by the * converter. You can do the conversion simply by calling * convertToContextFreeGrammar, or you can perform the conversion step by step * repeatedly calling getProductionsForTransition on every transition in the pda * and adding all of the returned productions to your context free grammar. If * you do this for every transition in the pda, you will have an equivalent cfg. * * @see grammar.cfg.ContextFreeGrammar * * @author Ryan Cavalcante */ public class PDAToCFGConverter { /** * Creates an instance of PDAToCFGConverter. */ public PDAToCFGConverter() { initializeConverter(); } /** * Initializes converter for pda to cfg conversion (clears map and sets * unique id) */ public void initializeConverter() { MAP = new HashMap(); UNIQUE_ID = 0; } /** * Returns true if automaton has a single final state that is * entered if and only if the stack is empty. * * @param automaton * the automaton. * @return true if automaton has a single final state that is * entered if and only if the stack is empty. */ public boolean hasSingleFinalState(Automaton automaton) { State[] finalStates = automaton.getFinalStates(); if (finalStates.length != 1) { // System.err.println("There is not exactly one final state!"); return false; } State finalState = finalStates[0]; Transition[] transitions = automaton.getTransitionsToState(finalState); for (int k = 0; k < transitions.length; k++) { PDATransition trans = (PDATransition) transitions[k]; String toPop = trans.getStringToPop(); if (!(toPop.substring(toPop.length() - 1)).equals(BOTTOM_OF_STACK)) { // System.err.println("Bad transition to final state! "+trans); // System.err.println(toPop.substring(toPop.length()-1)); return false; } } return true; } /** * Returns true if all transitions in automaton read a single * character from the input, pop a single character from the stack and push * either zero or two characters on to the stack. * * @param automaton * the automaton * @return true if all transitions in automaton read a single * character from the input, pop a single character from the stack * and push either zero or two characters on to the stack. */ public boolean hasTransitionsInCorrectForm(Automaton automaton) { Transition[] transitions = automaton.getTransitions(); for (int k = 0; k < transitions.length; k++) { if (!isPushLambdaTransition(transitions[k]) && !isPushTwoTransition(transitions[k])) { return false; } } return true; } /** * Returns true if automaton is in the correct form to * perform the conversion to CFG. The correct form enforces two restrictions * on automaton : 1. it has a single final state that is * entered if and only if the stack is empty. 2. all transitions read a * single character from the input, pop a single character from the stack * and either push two or zero characters on to the stack. * * @param automaton * the automaton * @return true if automaton is in the correct form to * perform the conversion to CFG. */ public boolean isInCorrectFormForConversion(Automaton automaton) { if (hasSingleFinalState(automaton) && hasTransitionsInCorrectForm(automaton)) { return true; } return false; } /** * Returns true if transition reads a single character from * the input tape, pops a single character from the stack, and writes TWO * characters to the stack. * * @param transition * the transition * @return true if transition reads a single character from * the input tape, pops a single character from the stack, and * writes TWO characters to the stack. */ public boolean isPushTwoTransition(Transition transition) { PDATransition trans = (PDATransition) transition; String toPush = trans.getStringToPush(); if (toPush.length() != 2) return false; /* * String input = trans.getInputToRead(); if(input.length() != 1) return * false; */ String toPop = trans.getStringToPop(); if (toPop.length() != 1) return false; return true; } /** * Returns true if transition reads a single character from * the input tape, pops a single character from the stack, and writes NO * characters to the stack. * * @param transition * the transition * @return true if transition reads a single character from * the input tape, pops a single character from the stack, and * writes NO characters to the stack. */ public boolean isPushLambdaTransition(Transition transition) { PDATransition trans = (PDATransition) transition; String toPush = trans.getStringToPush(); if (toPush.length() != 0) return false; /* * String input = trans.getInputToRead(); if(input.length() != 1) return * false; */ String toPop = trans.getStringToPop(); if (toPop.length() != 1) return false; return true; } /** * Returns a unique variable. * * @return a unique variable. */ private String getUniqueVariable() { char[] ch = new char[1]; ch[0] = (char) ('A' + UNIQUE_ID); UNIQUE_ID++; if (('A' + UNIQUE_ID) == 'S') UNIQUE_ID++; return new String(ch); } /** * Returns true if variable is the start symbol. (i.e. * "(q0Zqf)") * * @param variable * the variable. * @param automaton * the automaton. * @return true if variable is the start symbol. */ public boolean isStartSymbol(String variable, Automaton automaton) { State startState = automaton.getInitialState(); State[] finalStates = automaton.getFinalStates(); if (finalStates.length > 1) { // System.err.println("MORE THAN ONE FINAL STATE"); return false; } State finalState = finalStates[0]; String startSymbol = LEFT_PAREN.concat(startState.getName().concat( BOTTOM_OF_STACK .concat(finalState.getName().concat(RIGHT_PAREN)))); if (variable.equals(startSymbol)) return true; return false; } /** * Returns a list of productions created for transition, a * transition that pushes TWO characters on the stack. * * @param transition * the transition * @param automaton * the automaton * @return a list of productions created for transition, a * transition that pushes TWO characters on the stack. */ public ArrayList getProductionsForPushTwoTransition(Transition transition, Automaton automaton) { ArrayList list = new ArrayList(); String fromState = transition.getFromState().getName(); String toState = transition.getToState().getName(); PDATransition trans = (PDATransition) transition; String toPop = trans.getStringToPop(); String toRead = trans.getInputToRead(); String toPush = trans.getStringToPush(); String toPushOne = toPush.substring(0, 1); String toPushTwo = toPush.substring(1); State[] states = automaton.getStates(); for (int k = 0; k < states.length; k++) { String state = states[k].getName(); String lhs = LEFT_PAREN.concat(fromState.concat(toPop.concat(state .concat(RIGHT_PAREN)))); for (int j = 0; j < states.length; j++) { String lstate = states[j].getName(); String variable1 = LEFT_PAREN.concat(toState.concat(toPushOne .concat(lstate.concat(RIGHT_PAREN)))); String variable2 = LEFT_PAREN.concat(lstate.concat(toPushTwo .concat(state.concat(RIGHT_PAREN)))); /** Map to unique variables. */ if (MAP.get(lhs) == null) { if (isStartSymbol(lhs, automaton)) MAP.put(lhs, START_SYMBOL); else MAP.put(lhs, getUniqueVariable()); } if (MAP.get(variable1) == null) { if (isStartSymbol(variable1, automaton)) MAP.put(variable1, START_SYMBOL); else MAP.put(variable1, getUniqueVariable()); } if (MAP.get(variable2) == null) { if (isStartSymbol(variable2, automaton)) MAP.put(variable2, START_SYMBOL); else MAP.put(variable2, getUniqueVariable()); } String rhs = toRead.concat(variable1.concat(variable2)); Production p = new Production(lhs, rhs); list.add(p); } } return list; } /** * Returns a list of productions created for transition, a * transition that pushes NO characters on the stack. This list will always * contain a single production. * * @param transition * the transition * @return a list of productions created for transition, a * transition that pushes NO characters on the stack. This list will * always contain a single produciton. */ public ArrayList getProductionsForPushLambdaTransition( Transition transition, Automaton automaton) { ArrayList list = new ArrayList(); String fromState = transition.getFromState().getName(); String toState = transition.getToState().getName(); PDATransition trans = (PDATransition) transition; String toPop = trans.getStringToPop(); String toRead = trans.getInputToRead(); String lhs = LEFT_PAREN.concat(fromState.concat(toPop.concat(toState .concat(RIGHT_PAREN)))); if (MAP.get(lhs) == null) { if (isStartSymbol(lhs, automaton)) MAP.put(lhs, START_SYMBOL); else MAP.put(lhs, getUniqueVariable()); } String rhs = toRead; Production production = new Production(lhs, rhs); list.add(production); return list; } /** * Returns a list of productions that represent the same functionality as * transition in automaton. * * @param transition * the transition * @param automaton * the automaton that transition is a part of. * @return a list of productions. */ public ArrayList createProductionsForTransition(Transition transition, Automaton automaton) { ArrayList list = new ArrayList(); if (isPushLambdaTransition(transition)) { list.addAll(getProductionsForPushLambdaTransition(transition, automaton)); } else if (isPushTwoTransition(transition)) { list.addAll(getProductionsForPushTwoTransition(transition, automaton)); } return list; } /** * Returns an equivalent production to production but with * each variable (e.g. "q1Aq3") replaced by a unique variable (e.g. "B"); * * @param production * the production * @return an equivalent production to production with a * single variable replacing groups of characters. */ public Production getSimplifiedProduction(Production production) { String lhs = (String) MAP.get(production.getLHS()); String rhs = production.getRHS(); int leftIndex, rightIndex; // Position of left and right parentheses. StringBuffer newRhs = new StringBuffer(); while ((leftIndex = rhs.indexOf('(')) != -1 && (rightIndex = rhs.indexOf(')')) != -1) { newRhs.append(rhs.substring(0, leftIndex)); String variable = rhs.substring(leftIndex, rightIndex + 1); newRhs.append(MAP.get(variable)); rhs = rhs.substring(rightIndex + 1); } newRhs.append(rhs); Production p = new Production(lhs, newRhs.toString()); return p; } /** * Returns the number of unique variables defined sofar in this conversion. * * @return the number of unique variables */ public int numberVariables() { return (new HashSet(MAP.values())).size(); } /** * Returns a ContextFreeGrammar object that represents a grammar equivalent * to automaton. * * @param automaton * the automaton. * @return a cfg equivalent to automaton. */ public ContextFreeGrammar convertToContextFreeGrammar(Automaton automaton) { /** check if automaton is pda. */ if (!(automaton instanceof PushdownAutomaton)) throw new IllegalArgumentException( "automaton must be PushdownAutomaton"); if (!isInCorrectFormForConversion(automaton)) throw new IllegalArgumentException( "automaton not in correct form for conversion to CFG"); initializeConverter(); ArrayList list = new ArrayList(); ContextFreeGrammar grammar = new ContextFreeGrammar(); Transition[] transitions = automaton.getTransitions(); for (int k = 0; k < transitions.length; k++) { list.addAll(createProductionsForTransition(transitions[k], automaton)); } Iterator it = list.iterator(); while (it.hasNext()) { Production p = (Production) it.next(); grammar.addProduction(getSimplifiedProduction(p)); } return grammar; } /** * Recursive function used by purgeProductions() to determine which productions should be * included in the grammar. It takes a variable and recursively checks all productions that have it on * the left-hand side to determine whether each production should be accepted * * @param lhs the variable on the left-hand side. The initial variable is the initial state + 'Z' + * the final state. * @param productions * the current list of productions. * @param valid * set of variables that potentially end in terminals * @param validProductions * an int array used to mark all productions that should not be removed. The values that * can be in it are:

-1 - production should be removed
0 - yet to be processed
* 1 - currently being processed (helps prevent cycling)
2 - production should be kept * @return true if one of the following three are true, false otherwise.

1. the lhs leads to a leaf node. *
2. the lhs leads to a cycle.
3. All variables on the right side lead to a cycle or a leaf node. */ private void purgeProductionsHelper(String lhs, Production[] productions, HashSet valid, int[] validProductions){ ArrayList variables; String rhs; for (int i=0; i -1) { variables.add(rhs.substring(rhs.indexOf(LEFT_PAREN), rhs.indexOf(RIGHT_PAREN)+1)); if (rhs.indexOf(RIGHT_PAREN) != rhs.length()-1) rhs = rhs.substring(rhs.indexOf(RIGHT_PAREN) + 1); else rhs = ""; } for (int j=0; j -1) { variables.push(rhs.substring(rhs.indexOf(LEFT_PAREN), rhs.indexOf(RIGHT_PAREN)+1)); if (rhs.indexOf(RIGHT_PAREN) != rhs.length()-1) rhs = rhs.substring(rhs.indexOf(RIGHT_PAREN) + 1); else rhs = ""; } while (variables.size() > 0) if (!valid.contains((String) variables.peek())) invalid.push(variables.pop()); else variables.pop(); if (invalid.size() == 0 && !valid.contains(productions[i].getLHS())) { updated = true; valid.add(productions[i].getLHS()); } } } while (updated); //Then, trace a path from the initial variable to all terminals that it can reach. String initVar = LEFT_PAREN + automaton.getInitialState().getName() + BOTTOM_OF_STACK + automaton.getFinalStates()[0].getName() + RIGHT_PAREN; purgeProductionsHelper(initVar, productions, valid, validProductions); //Next, delete all superfluous rows and make note of those capital-letter variable //assignments that are freed up in a new map. HashMap newMap = new HashMap(); HashSet freeValues = new HashSet(); String key; for (int i=0; i<26; i++) freeValues.add("" + (char)('A' + i)); for (int i=validProductions.length-1; i>=0; i--) if (validProductions[i] < 2) model.deleteRow(i); else { key = productions[i].getLHS(); newMap.put(key, MAP.get(key)); if (((String)MAP.get(key)).charAt(0) <= 'Z') freeValues.remove((String)MAP.get(key)); } //Finally, assign the new map to the old map, and assign one-letter variables to //any variables that need one. MAP = newMap; Iterator freeIter, mapIter; freeIter = freeValues.iterator(); mapIter = newMap.keySet().iterator(); while (mapIter.hasNext()) { key = (String) mapIter.next(); if (((String)MAP.get(key)).charAt(0) > 'Z') MAP.put(key, (String)freeIter.next()); } } protected static final String START_SYMBOL = "S"; protected int UNIQUE_ID; protected HashMap MAP; protected static final String LEFT_PAREN = "("; protected static final String RIGHT_PAREN = ")"; protected static final String BOTTOM_OF_STACK = "Z"; }