/*******************************************************************************
* This file is part of logisim-evolution.
*
* logisim-evolution is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* logisim-evolution 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with logisim-evolution. If not, see <http://www.gnu.org/licenses/>.
*
* Original code by Carl Burch (http://www.cburch.com), 2011.
* Subsequent modifications by :
* + Haute École Spécialisée Bernoise
* http://www.bfh.ch
* + Haute École du paysage, d'ingénierie et d'architecture de Genève
* http://hepia.hesge.ch/
* + Haute École d'Ingénierie et de Gestion du Canton de Vaud
* http://www.heig-vd.ch/
* The project is currently maintained by :
* + REDS Institute - HEIG-VD
* Yverdon-les-Bains, Switzerland
* http://reds.heig-vd.ch
*******************************************************************************/
package com.cburch.logisim.analyze.model;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
public class OutputExpressions {
private class MyListener implements VariableListListener,
TruthTableListener {
public void cellsChanged(TruthTableEvent event) {
String output = model.getOutputs().get(event.getColumn());
invalidate(output, false);
}
private void inputsChanged(VariableListEvent event) {
int type = event.getType();
if (type == VariableListEvent.ALL_REPLACED && !outputData.isEmpty()) {
outputData.clear();
fireModelChanged(OutputExpressionsEvent.ALL_VARIABLES_REPLACED);
} else if (type == VariableListEvent.REMOVE) {
String input = event.getVariable();
for (String output : outputData.keySet()) {
OutputData data = getOutputData(output, false);
if (data != null)
data.removeInput(input);
}
} else if (type == VariableListEvent.REPLACE) {
String input = event.getVariable();
int inputIndex = ((Integer) event.getData()).intValue();
String newName = event.getSource().get(inputIndex);
for (String output : outputData.keySet()) {
OutputData data = getOutputData(output, false);
if (data != null)
data.replaceInput(input, newName);
}
} else if (type == VariableListEvent.MOVE
|| type == VariableListEvent.ADD) {
for (String output : outputData.keySet()) {
OutputData data = getOutputData(output, false);
if (data != null)
data.invalidate(false, false);
}
}
}
public void listChanged(VariableListEvent event) {
if (event.getSource() == model.getInputs())
inputsChanged(event);
else
outputsChanged(event);
}
private void outputsChanged(VariableListEvent event) {
int type = event.getType();
if (type == VariableListEvent.ALL_REPLACED && !outputData.isEmpty()) {
outputData.clear();
fireModelChanged(OutputExpressionsEvent.ALL_VARIABLES_REPLACED);
} else if (type == VariableListEvent.REMOVE) {
outputData.remove(event.getVariable());
} else if (type == VariableListEvent.REPLACE) {
String oldName = event.getVariable();
if (outputData.containsKey(oldName)) {
OutputData toMove = outputData.remove(oldName);
int inputIndex = ((Integer) event.getData()).intValue();
String newName = event.getSource().get(inputIndex);
toMove.output = newName;
outputData.put(newName, toMove);
}
}
}
public void structureChanged(TruthTableEvent event) {
}
}
private class OutputData {
String output;
int format;
Expression expr = null;
String exprString = null;
List<Implicant> minimalImplicants = null;
Expression minimalExpr = null;
private boolean invalidating = false;
OutputData(String output) {
this.output = output;
invalidate(true, false);
}
Expression getExpression() {
return expr;
}
String getExpressionString() {
if (exprString == null) {
if (expr == null)
invalidate(false, false);
exprString = expr == null ? "" : expr.toString();
}
return exprString;
}
Expression getMinimalExpression() {
if (minimalExpr == null)
invalidate(false, false);
return minimalExpr;
}
List<Implicant> getMinimalImplicants() {
return minimalImplicants;
}
int getMinimizedFormat() {
return format;
}
private void invalidate(boolean initializing, boolean formatChanged) {
if (invalidating)
return;
invalidating = true;
try {
List<Implicant> oldImplicants = minimalImplicants;
Expression oldMinExpr = minimalExpr;
minimalImplicants = Implicant.computeMinimal(format, model,
output);
minimalExpr = Implicant.toExpression(format, model,
minimalImplicants);
boolean minChanged = !implicantsSame(oldImplicants,
minimalImplicants);
if (!updatingTable) {
// see whether the expression is still consistent with the
// truth table
TruthTable table = model.getTruthTable();
Entry[] outputColumn = computeColumn(model.getTruthTable(),
expr);
int outputIndex = model.getOutputs().indexOf(output);
Entry[] currentColumn = table.getOutputColumn(outputIndex);
if (!columnsMatch(currentColumn, outputColumn)
|| isAllUndefined(outputColumn) || formatChanged) {
// if not, then we need to change the expression to
// maintain consistency
boolean exprChanged = expr != oldMinExpr || minChanged;
expr = minimalExpr;
if (exprChanged) {
exprString = null;
if (!initializing) {
fireModelChanged(
OutputExpressionsEvent.OUTPUT_EXPRESSION,
output);
}
}
}
}
if (!initializing && minChanged) {
fireModelChanged(OutputExpressionsEvent.OUTPUT_MINIMAL,
output);
}
} finally {
invalidating = false;
}
}
boolean isExpressionMinimal() {
return expr == minimalExpr;
}
private void removeInput(String input) {
Expression oldMinExpr = minimalExpr;
minimalImplicants = null;
minimalExpr = null;
if (exprString != null) {
exprString = null; // invalidate it so it recomputes
}
if (expr != null) {
Expression oldExpr = expr;
Expression newExpr;
if (oldExpr == oldMinExpr) {
newExpr = getMinimalExpression();
expr = newExpr;
} else {
newExpr = expr.removeVariable(input);
}
if (newExpr == null || !newExpr.equals(oldExpr)) {
expr = newExpr;
fireModelChanged(OutputExpressionsEvent.OUTPUT_EXPRESSION,
output, expr);
}
}
fireModelChanged(OutputExpressionsEvent.OUTPUT_MINIMAL, output,
minimalExpr);
}
private void replaceInput(String input, String newName) {
minimalExpr = null;
if (exprString != null) {
exprString = Parser.replaceVariable(exprString, input, newName);
}
if (expr != null) {
Expression newExpr = expr.replaceVariable(input, newName);
if (!newExpr.equals(expr)) {
expr = newExpr;
fireModelChanged(OutputExpressionsEvent.OUTPUT_EXPRESSION,
output);
}
} else {
fireModelChanged(OutputExpressionsEvent.OUTPUT_EXPRESSION,
output);
}
fireModelChanged(OutputExpressionsEvent.OUTPUT_MINIMAL, output);
}
void setExpression(Expression newExpr, String newExprString) {
expr = newExpr;
exprString = newExprString;
if (expr != minimalExpr) { // for efficiency to avoid recomputation
Entry[] values = computeColumn(model.getTruthTable(), expr);
int outputColumn = model.getOutputs().indexOf(output);
updatingTable = true;
try {
model.getTruthTable().setOutputColumn(outputColumn, values);
} finally {
updatingTable = false;
}
}
fireModelChanged(OutputExpressionsEvent.OUTPUT_EXPRESSION, output,
getExpression());
}
void setMinimizedFormat(int value) {
if (format != value) {
format = value;
this.invalidate(false, true);
}
}
}
private static boolean columnsMatch(Entry[] a, Entry[] b) {
if (a.length != b.length)
return false;
for (int i = 0; i < a.length; i++) {
if (a[i] != b[i]) {
boolean bothDefined = (a[i] == Entry.ZERO || a[i] == Entry.ONE)
&& (b[i] == Entry.ZERO || b[i] == Entry.ONE);
if (bothDefined)
return false;
}
}
return true;
}
private static Entry[] computeColumn(TruthTable table, Expression expr) {
int rows = table.getRowCount();
int cols = table.getInputColumnCount();
Entry[] values = new Entry[rows];
if (expr == null) {
Arrays.fill(values, Entry.DONT_CARE);
} else {
Assignments assn = new Assignments();
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
assn.put(table.getInputHeader(j),
TruthTable.isInputSet(i, j, cols));
}
values[i] = expr.evaluate(assn) ? Entry.ONE : Entry.ZERO;
}
}
return values;
}
private static boolean implicantsSame(List<Implicant> a, List<Implicant> b) {
if (a == null) {
return b == null || b.size() == 0;
} else if (b == null) {
return a == null || a.size() == 0;
} else if (a.size() != b.size()) {
return false;
} else {
Iterator<Implicant> ait = a.iterator();
for (Implicant bi : b) {
if (!ait.hasNext())
return false; // should never happen
Implicant ai = ait.next();
if (!ai.equals(bi))
return false;
}
return true;
}
}
private static boolean isAllUndefined(Entry[] a) {
for (int i = 0; i < a.length; i++) {
if (a[i] == Entry.ZERO || a[i] == Entry.ONE)
return false;
}
return true;
}
private MyListener myListener = new MyListener();
private AnalyzerModel model;
private HashMap<String, OutputData> outputData = new HashMap<String, OutputData>();
private ArrayList<OutputExpressionsListener> listeners = new ArrayList<OutputExpressionsListener>();
private boolean updatingTable = false;
public OutputExpressions(AnalyzerModel model) {
this.model = model;
model.getInputs().addVariableListListener(myListener);
model.getOutputs().addVariableListListener(myListener);
model.getTruthTable().addTruthTableListener(myListener);
}
//
// listener methods
//
public void addOutputExpressionsListener(OutputExpressionsListener l) {
listeners.add(l);
}
private void fireModelChanged(int type) {
fireModelChanged(type, null, null);
}
private void fireModelChanged(int type, String variable) {
fireModelChanged(type, variable, null);
}
private void fireModelChanged(int type, String variable, Object data) {
OutputExpressionsEvent event = new OutputExpressionsEvent(model, type,
variable, data);
for (OutputExpressionsListener l : listeners) {
l.expressionChanged(event);
}
}
//
// access methods
//
public Expression getExpression(String output) {
if (output == null)
return null;
return getOutputData(output, true).getExpression();
}
public String getExpressionString(String output) {
if (output == null)
return "";
return getOutputData(output, true).getExpressionString();
}
public Expression getMinimalExpression(String output) {
if (output == null)
return Expressions.constant(0);
return getOutputData(output, true).getMinimalExpression();
}
public List<Implicant> getMinimalImplicants(String output) {
if (output == null)
return Implicant.MINIMAL_LIST;
return getOutputData(output, true).getMinimalImplicants();
}
public int getMinimizedFormat(String output) {
if (output == null)
return AnalyzerModel.FORMAT_SUM_OF_PRODUCTS;
return getOutputData(output, true).getMinimizedFormat();
}
private OutputData getOutputData(String output, boolean create) {
if (output == null)
throw new IllegalArgumentException("null output name");
OutputData ret = outputData.get(output);
if (ret == null && create) {
if (model.getOutputs().indexOf(output) < 0) {
throw new IllegalArgumentException("unrecognized output "
+ output);
}
ret = new OutputData(output);
outputData.put(output, ret);
}
return ret;
}
private void invalidate(String output, boolean formatChanged) {
OutputData data = getOutputData(output, false);
if (data != null)
data.invalidate(false, false);
}
public boolean isExpressionMinimal(String output) {
OutputData data = getOutputData(output, false);
return data == null ? true : data.isExpressionMinimal();
}
public void removeOutputExpressionsListener(OutputExpressionsListener l) {
listeners.remove(l);
}
public void setExpression(String output, Expression expr) {
setExpression(output, expr, null);
}
public void setExpression(String output, Expression expr, String exprString) {
if (output == null)
return;
getOutputData(output, true).setExpression(expr, exprString);
}
//
// modifier methods
//
public void setMinimizedFormat(String output, int format) {
int oldFormat = getMinimizedFormat(output);
if (format != oldFormat) {
getOutputData(output, true).setMinimizedFormat(format);
invalidate(output, true);
}
}
}