/*
* 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 grammar.parse;
import grammar.Grammar;
import grammar.Production;
import java.util.*;
/**
* The BruteParser
is an abstract class that will perform a brute
* force parse of a grammar.
*
* @author Thomas Finley
*/
public abstract class BruteParser {
/**
* This does nothing. One can use this constructor if one wants to call init
* later.
*/
protected BruteParser() {
}
/**
* This will instantiate a new brute parser. All this does is call
* {@link #init} with the grammar and target string.
*/
public BruteParser(Grammar grammar, String target) {
init(grammar, target);
}
/**
* This factory method will return a brute force parser appropriate for the
* grammar.
*
* @param grammar
* the grammar to get a brute force parser for
* @param target
* the target string
*/
public static BruteParser get(Grammar grammar, String target) {
if (Unrestricted.isUnrestricted(grammar))
return new UnrestrictedBruteParser(grammar, target);
return new RestrictedBruteParser(grammar, target);
}
/**
* This will initialize data structures.
*/
protected void init(Grammar grammar, String target) {
for (int i = 0; i < target.length(); i++)
if (!grammar.isTerminal(target.substring(i, i + 1)))
throw new IllegalArgumentException(
"String to parse has nonterminal "
+ target.substring(i, i + 1) + ".");
queue.clear();
grammar = Unrestricted.optimize(grammar);
if (grammar == null)
return;
queue.add(new ParseNode(grammar.getStartVariable(), P, S));
consideredNodes = 0;
deletedNodes = 0;
smaller = Collections.unmodifiableSet(Unrestricted
.smallerSymbols(grammar));
this.grammar = grammar;
productions = grammar.getProductions();
this.target = target;
}
/**
* This will start the parsing. This method will return immediately. The
* parsing is done in a separate thread since the potential for the parsing
* to take forever on some brute force parses exists.
*
* @return if the starting of the parsing was successful, which will not be
* successful if the parsing is already underway, or if the parser
* is finished
*/
public synchronized boolean start() {
if (isActive() || isFinished())
return false;
parseThread = new Thread() {
public void run() {
while (parseThread != null)
parse();
}
};
parseThread.start();
distributeEvent(new BruteParserEvent(this, BruteParserEvent.START));
return true;
}
/**
* This will pause the parsing. At the end of this method the parsing thread
* will probably not halt.
*/
public synchronized void pause() {
parseThread = null;
distributeEvent(new BruteParserEvent(this, BruteParserEvent.PAUSE));
}
/**
* Returns if the parser is currently in the process of parsing.
*
* @return true
if the parser thread is currently active, or
* false
if the parser thread is inactive
*/
public synchronized boolean isActive() {
return parseThread != null;
}
/**
* Returns if the parser has finished, with success or otherwise.
*
* @return true
if the
*/
public synchronized boolean isFinished() {
return isDone;
}
/**
* This returns the answer node for the parser.
*
* @return the answer node for the parse, or null
if there
* was no answer, or one has not been discovered yet
*/
public synchronized ParseNode getAnswer() {
return answer;
}
/**
* Returns a list of possible one step parses for a given string. The first
* entry is always the identity.
*/
private List getPossibilities(String c) {
List possibilities = new ArrayList();
if (prederived.containsKey(c))
return (List) prederived.get(c);
HashSet alreadyEncountered = new HashSet();
if (c.length() == 0) {
possibilities.add(E);
return possibilities;
}
for (int i = -1; i < productions.length; i++) {
Production prod = i == -1 ? new Production(c.substring(0, 1), c
.substring(0, 1)) : productions[i];
// Find the start of the production.
int start = c.indexOf(prod.getLHS());
int lengthSubs = prod.getLHS().length();
if (start == -1)
continue;
List list = getPossibilities(c.substring(start + lengthSubs));
Iterator it = list.iterator();
String prepend = c.substring(0, start) + prod.getRHS();
int lengthReplace = start + prod.getLHS().length();
// Make adjustments for each entry.
while (it.hasNext()) {
ParseNode node = (ParseNode) it.next();
String d = node.getDerivation();
Production[] p = node.getProductions();
String a = prepend + d;
int[] s = node.getSubstitutions();
if (i == -1) {
int[] newS = new int[s.length];
for (int j = 0; j < p.length; j++) {
newS[j] = s[j] + lengthReplace;
}
// Make the node with the substitution.
if (alreadyEncountered.add(a)) {
node = new ParseNode(a, p, newS);
possibilities.add(node);
beingConsideredNodes++;
}
} else {
Production[] newP = new Production[p.length + 1];
int[] newS = new int[s.length + 1];
newS[0] = start;
newP[0] = prod;
for (int j = 0; j < p.length; j++) {
newP[j + 1] = p[j];
newS[j + 1] = s[j] + lengthReplace;
}
// Make the node with the substitution.
if (alreadyEncountered.add(a)) {
node = new ParseNode(a, newP, newS);
possibilities.add(node);
beingConsideredNodes++;
}
}
}
}
// prederived.put(c, possibilities);
return possibilities;
}
// Stuff for the possibilities.
private static final Production[] P = new Production[0];
private static final int[] S = new int[0];
private static final ParseNode E = new ParseNode("", P, S);
/**
* Any node that is not accepted and can have no children is futile. Since
* the tree is doubly linked, the entire BFS tree will be preserved. By
* removing futile nodes, this allows recursion to proceed much deeper. If
* the node passed in has no children, it is removed from the parent, and we
* recurse on the parent.
*
* @param node
* a possibly futile node
*/
private void removeFutility(ParseNode node) {
try {
while (node.isLeaf()) {
((ParseNode) node.getParent()).remove(node);
deletedNodes++;
node = (ParseNode) node.getParent();
}
} catch (NullPointerException e) {
// The parent didn't exist, so we're done.
}
}
/**
* Returns the number of nodes currently in the tree.
*
* @return number of nodes in the tree whose paths have not been ruled out
*/
public int getCurrentNodeCount() {
return consideredNodes - deletedNodes;
}
/**
* Returns the number of nodes generated.
*
* @return number of nodes in the tree, even those that have been ruled out
* by now
*/
public int getTotalNodeCount() {
return consideredNodes;
}
/**
* Returns the number of nodes on the current "consideration" queue. These
* nodes have not yet been added.
*/
public int getConsiderationNodeCount() {
return beingConsideredNodes;
}
/**
* Given a string, this method should indicate whether or not this
* derivation could eventually result in our target. Note that returning
* true
does not mean that this derivation can actually
* result in the target, just that it has not been firmly ruled out. This
* method is used for optimization purposes by the parser.
*
* @param derivation
*/
public boolean isPossibleDerivation(String derivation) {
return Unrestricted.minimumLength(derivation, smaller) <= target
.length();
}
/**
* The parsing method.
*/
private synchronized void parse() {
if (queue.isEmpty()) {
isDone = true;
parseThread = null;
distributeEvent(new BruteParserEvent(this, BruteParserEvent.REJECT));
return;
}
// Get one element.
ParseNode node = (ParseNode) queue.removeFirst();
beingConsideredNodes = 0;
List pos = getPossibilities(node.getDerivation());
beingConsideredNodes = 0;
Iterator it = pos.iterator();
while (it.hasNext()) {
ParseNode pNode = (ParseNode) it.next();
if (!alreadyAdded.add(pNode.getDerivation()))
continue;
if (!isPossibleDerivation(pNode.getDerivation()))
continue;
pNode = new ParseNode(pNode);
node.add(pNode);
queue.add(pNode);
consideredNodes++;
if (pNode.getDerivation().equals(target)) {
answer = pNode;
isDone = true;
parseThread = null;
queue.clear();
distributeEvent(new BruteParserEvent(this,
BruteParserEvent.ACCEPT));
return;
}
}
// Was anything added?
if (node.isLeaf())
removeFutility(node);
}
/**
* Adds a brute parser listener to this parser.
*
* @param listener
* the listener to add
*/
public void addBruteParserListener(BruteParserListener listener) {
listeners.add(listener);
}
/**
* Removes a brute parser listener from this parser.
*
* @param listener
* the listener to remove
*/
public void removeBruteParserListener(BruteParserListener listener) {
listeners.remove(listener);
}
/**
* Distributes a brute parser event to all listeners.
*
* @param event
* the brute parser event to distribute
*/
protected void distributeEvent(BruteParserEvent event) {
Iterator it = listeners.iterator();
while (it.hasNext())
((BruteParserListener) it.next()).bruteParserStateChange(event);
}
/** The set of listeners. */
protected Set listeners = new HashSet();
/** This is the grammar. */
protected Grammar grammar;
/** The array of productions. */
protected Production[] productions;
/** This is the target string. */
protected String target;
/** This should be set to done when the operation has completed. */
private boolean isDone = false;
/**
* This is the thread that does the parsing; if the value is null
* that indicates that no parsing thread exists yet.
*/
private Thread parseThread = null;
/** This set holds those strings already added to the tree. */
private Set alreadyAdded = new HashSet();
/**
* This holds those strings that have already been derived, with a map to
* those nodes for each string.
*/
private Map prederived = new HashMap();
/** This holds the list of nodes for the BFS. */
protected LinkedList queue = new LinkedList();
/** The number of explored nodes. */
private int consideredNodes = 0;
/** The number of unexplored but perhaps soon to be explored nodes. */
private int beingConsideredNodes = 0;
/** The number of deleted nodes. */
private int deletedNodes = 0;
/** The "answer" to the parse question. */
private ParseNode answer = null;
/**
* The "smaller" set, those symbols that may possibly reduce to nothing.
*/
protected Set smaller;
}