/* Copyright 2009-2016 David Hadka
*
* This file is part of the MOEA Framework.
*
* The MOEA Framework is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* The MOEA Framework is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the MOEA Framework. If not, see <http://www.gnu.org/licenses/>.
*/
package org.moeaframework.util.grammar;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Stack;
/**
* A context-free grammar. The rule at index {@code 0} is the starting rule when
* building derivation trees.
*
* @see Rule
*/
public class ContextFreeGrammar {
/**
* The rules contained in this grammar.
*/
private final List<Rule> rules;
/**
* The maximum number of times the builder will wrap around the codon array
* before failing to produce a valid derivation.
*/
private int wrapLimit;
/**
* Constructs an empty grammar.
*/
public ContextFreeGrammar() {
super();
rules = new ArrayList<Rule>();
wrapLimit = 10;
}
/**
* Returns the maximum number of times the builder will wrap around the
* codon array before failing to produce a valid derivation.
*
* @return the maximum number of times the builder will wrap around the
* codon array before failing to produce a valid derivation
*/
public int getWrapLimit() {
return wrapLimit;
}
/**
* Sets the maximum number of times the builder will wrap around the codon
* array before failing to produce a valid derivation.
*
* @param wrapLimit the maximum number of times the builder will wrap around
* the codon array before failing to produce a valid derivation
*/
public void setWrapLimit(int wrapLimit) {
this.wrapLimit = wrapLimit;
}
/**
* Adds a rule to this grammar.
*
* @param rule the rule to be added
*/
public void add(Rule rule) {
rules.add(rule);
}
/**
* Removes a rule from this grammar.
*
* @param rule the rule to be removed
*/
public void remove(Rule rule) {
rules.remove(rule);
}
/**
* Returns the number of rules contained in this grammar.
*
* @return the number of rules contained in this grammar
*/
public int size() {
return rules.size();
}
/**
* Returns the rule at the specified index.
*
* @param index the index of the rule to be returned
* @return the rule at the specified index
* @throws IndexOutOfBoundsException if index is out of range {@code ((index
* < 0) || (index >= size())}
*/
public Rule get(int index) {
return rules.get(index);
}
/**
* Returns the rule for the specified symbol; or {@code null} if no rule
* with the specified symbol exists.
*
* @param symbol the symbol of the rule to be returned
* @return the rule for the specified symbol; or {@code null} if no rule
* with the specified symbol exists
*/
public Rule get(Symbol symbol) {
for (Rule rule : rules) {
if (rule.getSymbol().equals(symbol)) {
return rule;
}
}
return null;
}
/**
* Returns the grammar derivation using the construction rules of
* Grammatical Evolution on the specified codon array; or {@code null} if
* the codon array failed to produce a valid derivation Whenever the
* derivation encounters a rule with multiple productions, the production
* used to expand the rule is chosen using this codon array.
*
* @param array the codon array
* @return the grammar derivation using the construction rules of
* Grammatical Evolution on the specified codon array; or
* {@code null} if the codon array failed to produce a valid
* derivation
* @throws GrammarException if the codon array is empty
*/
public String build(int[] array) {
if (array.length == 0) {
throw new GrammarException("codon array is empty");
}
StringBuilder sb = new StringBuilder();
Stack<Symbol> remaining = new Stack<Symbol>();
int index = 0;
int wraps = 0;
remaining.push(rules.get(0).getSymbol());
while (!remaining.isEmpty()) {
Symbol symbol = remaining.pop();
if (symbol.isTerminal()) {
sb.append(symbol.getValue());
} else {
Rule rule = get(symbol);
int productionIndex = 0;
if (rule.size() > 1) {
productionIndex = array[index] % rule.size();
index++;
if (index >= array.length) {
index = 0;
wraps++;
if (wraps > wrapLimit) {
return null;
}
}
}
Production production = rule.get(productionIndex);
for (int i = production.size() - 1; i >= 0; i--) {
remaining.push(production.get(i));
}
}
}
return sb.toString();
}
/**
* Returns {@code true} if this grammar is valid; {@code false} otherwise. A
* grammar is valid if it is non-empty and all non-terminal symbols are
* defined by a rule in the grammar.
*
* @return {@code true} if this grammar is valid; {@code false} otherwise
*/
public boolean isValid() {
if (size() == 0) {
return false;
}
Set<Symbol> symbols = new HashSet<Symbol>();
for (int i = 0; i < size(); i++) {
Rule rule = get(i);
for (int j = 0; j < rule.size(); j++) {
Production production = rule.get(j);
for (int k = 0; k < production.size(); k++) {
Symbol symbol = production.get(k);
if (!symbol.isTerminal()) {
symbols.add(symbol);
}
}
}
}
for (Symbol symbol : symbols) {
if (get(symbol) == null) {
return false;
}
}
return true;
}
}