/*
* xtc - The eXTensible Compiler
* Copyright (C) 2009-2011 New York University
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation.
*
* This library 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 this library; if not, write to the Free Software
* Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*/
package xtc.lang.cpp;
import java.util.List;
import java.io.StringReader;
import xtc.tree.GNode;
import xtc.tree.Node;
import xtc.tree.Token;
import xtc.tree.Visitor;
import xtc.type.C;
import xtc.type.Type;
import xtc.parser.ParseException;
import xtc.parser.Result;
import xtc.lang.cpp.ContextManager.Context;
import xtc.lang.cpp.MacroTable.Macro;
import xtc.lang.cpp.MacroTable.Entry;
import net.sf.javabdd.BDD;
import net.sf.javabdd.BDDFactory;
/** A visitor to partially evaluate conditional expressions that may contain
* both constants and free variable. The visitor evaluates constants as
* much as possible. However, if a free variable is used in a non-logical
* operation, a new free variable is made to represent the expression.
*
* @author Paul Gazzillo
*/
public class ConditionEvaluator extends Visitor {
/**
* The macro table used for the defined operator. If null, no resolution
* is done.
*/
private MacroTable macroTable;
/*
* The context in which the conditional expression is evaluated.
* This is use to trim infeasible macro definitions for the defined
* operator and must be non-null when macroTable is non-null.
*/
private ContextManager contextManager;
/** The BDD factory. */
private BDDFactory B;
/** The common type operations for C. */
private final C cops;
/**
* When this flag is true, this evaluator produces a string
* representation of the expression instead of a BDD. It is only
* tripped when the evaluator encounters non-boolean expressions.
* Once the string reaches boolean operators, it is turned off again
* and, in its entirety, the non-boolean expression becomes a
* boolean variable.
*/
private boolean dostring = false;
/**
* This flag is turned on when the evaluator sees a nonboolean
* subexpression.
*/
private boolean nonboolean = false;
/** Construct a new expression evaluator with a macro table for defined
* operations.
*/
public ConditionEvaluator(ContextManager contextManager, MacroTable macroTable) {
this.contextManager = contextManager;
this.macroTable = macroTable;
this.B = contextManager.getBDDFactory();
this.cops = new C();
}
public BDD evaluate(String expression) {
BDD expressionBDD;
try {
ConditionParser parser;
StringReader reader;
Result result;
Node tree;
reader = new StringReader(expression);
parser = new ConditionParser(
reader,
"EXPRESSION",
expression.length()
);
try {
result = parser.pConstantExpression(0);
tree = (Node) parser.value(result);
//TODO if the parser is not at the end of input, expression syntax error
nonboolean = false;
expressionBDD = ensureBDD(dispatch(tree));
result = null;
tree = null;
} catch (Exception e) {
e.printStackTrace();
System.err.println("could not parse conditional expression");
expressionBDD = B.zero();
tree = null;
}
reader.close();
parser = null;
}
catch (Exception e) {
expressionBDD = B.zero();
}
return expressionBDD;
}
/**
* Returns true if the evaluator saw a non-boolean subexpression in
* the last expression it evaluated.
*/
public boolean sawNonboolean() {
return nonboolean;
}
/** Process an integer constant. */
public Object visitIntegerConstant(GNode n) {
xtc.type.Type result;
result = cops.typeInteger(n.getString(0));
return result.getConstant().bigIntValue().longValue();
}
/** Process a character constant. */
public Object visitCharacterConstant(GNode n) {
xtc.type.Type result;
result = cops.typeCharacter(n.getString(0));
return result.getConstant().bigIntValue().longValue();
}
/** Process primary identifier. */
public Object visitPrimaryIdentifier(GNode n) {
return n.getString(0);
}
/** Process a unary minus. */
public Object visitUnaryMinusExpression(GNode n) {
Object a;
nonboolean = true;
dostring = true;
a = dispatch(n.getGeneric(0));
dostring = false;
if (a instanceof Long) {
return - (Long) a;
}
else {
return "- " + parens(a);
}
}
/** Process a unary plus. */
public Object visitUnaryPlusExpression(GNode n) {
nonboolean = true;
return dispatch(n.getGeneric(0));
}
/** Process a logical negation. */
public Object visitLogicalNegationExpression(GNode n) {
if (dostring) {
Object a = dispatch(n.getGeneric(0));
if (a instanceof Long) {
return "" + ((((Long) a) == 0) ? 1 : 0);
}
else {
return "! " + parens(a);
}
}
else {
BDD a = ensureBDD(dispatch(n.getGeneric(0)));
BDD bdd;
bdd = a.not();
a.free();
return bdd;
}
}
/** Process a bitwise negation. */
public Object visitBitwiseNegationExpression(GNode n) {
Object a, result;
nonboolean = true;
dostring = true;
a = dispatch(n.getGeneric(0));
dostring = false;
if (a instanceof Long) {
result = ~ (Long) a;
}
else {
return "~ " + parens(a);
}
return result;
}
/** Process a multiplicative operation. */
public Object visitMultiplicativeExpression(GNode n) {
Object a, b, result;
String op;
nonboolean = true;
dostring = true;
a = dispatch(n.getGeneric(0));
b = dispatch(n.getGeneric(2));
op = n.getString(1);
dostring = false;
if (a instanceof Long && b instanceof Long) {
if ((op.equals("/") || op.equals("%")) && (Long) b == 0) {
System.err.println("division by zero in #if");
result = 0;
}
if (op.equals("*")) {
result = (Long) a * (Long) b;
}
else if (op.equals("/")) {
result = (Long) a / (Long) b;
}
else if (op.equals("%")) {
result = (Long) a % (Long) b;
}
else {
result = "";
}
}
else {
result = parens(a) + " " + op + " " + parens(b);
}
return result;
}
/** Process an additive operation. */
public Object visitAdditiveExpression(GNode n) {
Object a, b, result;
String op;
nonboolean = true;
dostring = true;
a = dispatch(n.getGeneric(0));
b = dispatch(n.getGeneric(2));
op = n.getString(1);
dostring = false;
if (a instanceof Long && b instanceof Long) {
if (op.equals("+")) {
result = (Long) a + (Long) b;
}
else if (op.equals("-")) {
result = (Long) a - (Long) b;
}
else {
result = "";
}
}
else {
result = parens(a) + " " + op + " " + parens(b);
}
return result;
}
/** Process a shift expression. */
public Object visitShiftExpression(GNode n) {
Object a, b, result;
String op;
nonboolean = true;
dostring = true;
a = dispatch(n.getGeneric(0));
b = dispatch(n.getGeneric(2));
op = n.getString(1);
dostring = false;
if (a instanceof Long && b instanceof Long) {
if (op.equals("<<")) {
result = (Long) a << (Long) b;
}
else if (op.equals(">>")) {
result = (Long) a >> (Long) b;
}
else {
result = "";
}
}
else {
result = parens(a) + " " + op + " " + parens(b);
}
return result;
}
/** Process a relational expression. */
public Object visitRelationalExpression(GNode n) {
Object a, b, result;
String op;
nonboolean = true;
dostring = true;
a = dispatch(n.getGeneric(0));
b = dispatch(n.getGeneric(2));
op = n.getString(1);
dostring = false;
if (a instanceof Long && b instanceof Long) {
Long x = (Long) a;
Long y = (Long) b;
long zero = 0;
long one = 1;
if (op.equals("<")) {
result = x < y ? one : zero;
}
else if (op.equals("<=")) {
result = x <= y ? one : zero;
}
else if (op.equals(">")) {
result = x > y ? one : zero;
}
else if (op.equals(">=")) {
result = x >= y ? one : zero;
}
else {
result = "";
}
}
else {
result = parens(a) + " " + op + " " + parens(b);
}
return result;
}
/** Process a equality expression. */
public Object visitEqualityExpression(GNode n) {
Object a, b, result;
String op;
boolean mydostring = dostring;
nonboolean = true;
dostring = true;
a = dispatch(n.getGeneric(0));
b = dispatch(n.getGeneric(2));
op = n.getString(1);
dostring = false;
if (a instanceof Long && b instanceof Long) {
if (op.equals("==")) {
result = (Long) a == (Long) b;
}
else if (op.equals("!=")) {
result = (Long) a != (Long) b;
}
else {
result = "";
}
}
else {
String sa, sb;
if (a instanceof String) {
sa = (String) a;
}
else if (a instanceof Long) {
sa = ((Long) a).toString();
}
else {
return null;
}
if (b instanceof String) {
sb = (String) b;
}
else if (b instanceof Long) {
sb = ((Long) b).toString();
}
else {
return null;
}
if (op.equals("==") && sa.equals(sb)) {
result = mydostring ? "1" : B.one();
}
else {
result = parens(sa) + " " + op + " " + parens(sb);
}
}
return result;
}
/** Process a bitwise and. */
public Object visitBitwiseAndExpression(GNode n) {
Object a, b, result;
nonboolean = true;
dostring = true;
a = dispatch(n.getGeneric(0));
b = dispatch(n.getGeneric(1));
dostring = false;
if (a instanceof Long && b instanceof Long) {
result = (Long) a & (Long) b;
}
else {
result = parens(a) + " & " + parens(b);
}
return result;
}
/** Process a bitwise xor. */
public Object visitBitwiseXorExpression(GNode n) {
Object a, b, result;
nonboolean = true;
dostring = true;
a = dispatch(n.getGeneric(0));
b = dispatch(n.getGeneric(1));
dostring = false;
if (a instanceof Long && b instanceof Long) {
result = (Long) a ^ (Long) b;
}
else {
result = parens(a) + " ^ " + parens(b);
}
return result;
}
/** Process a bitwise or. */
public Object visitBitwiseOrExpression(GNode n) {
Object a, b, result;
nonboolean = true;
dostring = true;
a = dispatch(n.getGeneric(0));
b = dispatch(n.getGeneric(1));
dostring = false;
if (a instanceof Long && b instanceof Long) {
result = (Long) a | (Long) b;
}
else {
result = parens(a) + " | " + parens(b);
}
return result;
}
/** Process a logical and. */
public Object visitLogicalAndExpression(GNode n) {
if (dostring) {
Object a = dispatch(n.getGeneric(0));
Object b = dispatch(n.getGeneric(1));
if (a instanceof Long && b instanceof Long) {
return (Long) a & (Long) b;
}
else {
return parens(a) + " && " + parens(b);
}
}
else {
BDD a, b, bdd;
a = ensureBDD(dispatch(n.getGeneric(0)));
b = ensureBDD(dispatch(n.getGeneric(1)));
bdd = a.andWith(b);
return bdd;
}
}
/** Process a logical or. */
public Object visitLogicalOrExpression(GNode n) {
if (dostring) {
Object a = dispatch(n.getGeneric(0));
Object b = dispatch(n.getGeneric(1));
if (a instanceof Long && b instanceof Long) {
return (Long) a | (Long) b;
}
else {
return parens(a) + " || " + parens(b);
}
}
else {
BDD a, b, bdd;
a = ensureBDD(dispatch(n.getGeneric(0)));
b = ensureBDD(dispatch(n.getGeneric(1)));
bdd = a.orWith(b);
return bdd;
}
}
/** Make a new BDD argument, "defined M". If a macro table was supplied
* to the evaluator, look for M there and evaluate the operation.
*/
public Object visitDefinedExpression(GNode n) {
String parameter;
parameter = n.getGeneric(0).getString(0);
//evaluate the defined operation, preserving configurations
if (macroTable != null) {
List<Entry> definitions = macroTable.get(parameter, contextManager);
if (definitions != null && definitions.size() > 0) {
boolean hasDefined, hasUndefined, hasFree;
//three conditions
//1) defined under all configurations, so output 1 (true)
//2) undefined under all configurations, so output 0 (false)
//3) partially defined, so output union of configurations
hasDefined = false;
hasUndefined = false;
hasFree = false;
for (Entry def : definitions) {
if (def.macro.state == Macro.State.FREE) {
hasFree = true;
}
else if (def.macro.state == Macro.State.DEFINED) {
hasDefined = true;
}
else if (def.macro.state == Macro.State.UNDEFINED) {
hasUndefined = true;
}
}
//fully defined in this context
if (hasDefined && ! hasUndefined && ! hasFree) {
return B.one(); //the constant true BDD
}
//not defined in this context
else if (hasUndefined && ! hasDefined && ! hasFree) {
return B.zero(); //the constant false BDD
}
//partially defined in this context
else {
BDD defined = B.zero();
List<Token> tokenlist;
int c;
for (Entry def : definitions) {
if (def.macro.state == Macro.State.FREE) {
BDD newDefined;
BDD varBDD;
BDD term;
varBDD = contextManager.getVariableManager()
.getDefinedVariable(parameter);
term = def.context.getBDD().and(varBDD);
newDefined = defined.or(term);
term.free();
defined.free();
varBDD.free();
defined = newDefined;
}
else if (def.macro.state == Macro.State.DEFINED) {
BDD newDefined;
newDefined = defined.or(def.context.getBDD());
defined.free();
defined = newDefined;
}
else if (def.macro.state == Macro.State.UNDEFINED) {
//do nothing
}
}
return defined;
} //end partially defined
} //end has definitions
} //end has macro table
/*if (runtime.test("cppmode")) {
//return false in cpp mode
return "0";
}
else*/ {
//if there are no macro table entries, just return the operation as is
return "defined " + parameter; //return a string
}
}
/** Process a conditional expression. */
public Object visitConditionalExpression(GNode n) {
if (dostring) {
Object a = dispatch(n.getGeneric(0));
Object b = dispatch(n.getGeneric(1));
Object c = dispatch(n.getGeneric(2));
if (a instanceof Long) {
return ((Long) a != 0) ? b : c;
}
else {
return parens(a) + " ? " + parens(b) + " : " + parens(c);
}
}
else {
BDD a = ensureBDD(dispatch(n.getGeneric(0)));
BDD b = ensureBDD(dispatch(n.getGeneric(1)));
BDD c = ensureBDD(dispatch(n.getGeneric(2)));
BDD ab, na, nac, bdd;
//implement with a & b | !a & c
ab = a.and(b);
b.free();
na = a.not();
a.free();
nac = na.and(c);
c.free();
na.free();
bdd = ab.or(nac);
nac.free();
ab.free();
return bdd;
}
}
/** Ensures that parentheses surround terms to preserve order of operations
*/
public String parens(Object a) {
String s;
if (a instanceof String) {
s = (String) a;
}
else if (a instanceof Long) {
s = ((Long) a).toString();
}
else {
return null;
}
if (s.indexOf(" ") >= 0) {
return "(" + s + ")";
}
else {
return s;
}
}
/** Takes whatever the evaluation returns, string, boolean, or integer, and
* creates a BDD out of it if isn't already.
*/
public BDD ensureBDD(Object o) {
if (o instanceof BDD) {
return (BDD) o;
}
else if (o instanceof Long) {
if ((Long) o == 0) {
return B.zero();
}
else {
return B.one();
}
}
else if (o instanceof String) {
String s = parens(o);
return contextManager.getVariableManager().getVariable(s);
}
else if (o instanceof Boolean) {
Boolean b = (Boolean) o;
if (b) {
return B.one();
}
else {
return B.zero();
}
}
else {
System.err.println("FATAL: ensureBDD, unforeseen type from evaluator");
System.err.println(o);
System.err.println(o.getClass());
System.exit(-1);
return null;
}
}
}