/*
* 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.io.Writer;
import java.io.StringWriter;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import net.sf.javabdd.BDDFactory;
import net.sf.javabdd.BDD;
/** Conditional context manager. It abstracts away the nitty-gritty
* of using the BDD objects.
*
* @author Paul Gazzillo
* @version $Revision: 1.39 $
*/
class ContextManager {
/** The BDD factory. */
private BDDFactory B;
/** The variable name manager. */
private Variables vars;
/** The stack of nested presence conditions. */
private LinkedList<BDD> stack;
/**
* The current global presence condition, i.e. the conjunction of
* nested presence conditions.
*/
private BDD global;
/**
* The local presence condition of the current conditional branch.
*/
private BDD branch;
/**
* The not of the union of all previous conditional branches. This
* models the short-circuiting of preprocessor conditonals.
*/
private BDD notBranches;
/** The current context */
private Context current;
/**
* Create a new context manager.
*/
public ContextManager() {
this.B = BDDFactory.init(5000000, 50000);
this.vars = new Variables(B);
this.stack = new LinkedList<BDD>();
this.global = B.one();
this.branch = null;
this.notBranches = null;
this.current = null;
}
/**
* Copy a context manager.
*
* @param The context manager to copy.
*/
public ContextManager(ContextManager contextManager) {
this.B = contextManager.B;
this.vars = contextManager.vars;
this.stack = new LinkedList<BDD>();
for (BDD bdd : contextManager.stack) {
if (bdd != null) {
this.stack.add(bdd.id());
}
else {
this.stack.add(null);
}
}
this.global = contextManager.global.id();
if (null != contextManager.branch) {
this.branch = contextManager.branch.id();
}
else {
this.branch = null;
}
if (null != contextManager.notBranches) {
this.notBranches = contextManager.notBranches.id();
}
else {
this.notBranches = null;
}
this.current = null;
}
/**
* Free all BDDs held by this context manager. Note this does not
* free the BDDs generated by it, but only those it uses to track
* nested presence conditions.
*/
public void free() {
global.free();
if (null != branch) {
branch.free();
}
if (null != notBranches) {
notBranches.free();
}
for (BDD bdd : stack) {
if (null != bdd) {
bdd.free();
}
}
if (null != current) {
current.delRef();
current = null;
}
}
/**
* Push the current presence condition onto the stack.
*/
public void push() {
stack.push(notBranches);
stack.push(branch);
stack.push(global);
notBranches = B.one();
branch = B.zero();
global = B.zero();
if (null != current) {
current.delRef();
current = null;
}
}
/**
* Enters a new local context.
*
* @param bdd The new local context. Will be delRef'ed.
*/
public void enter(BDD bdd) {
notBranches.andWith(branch.not());
branch.free();
branch = bdd;
global.free();
// peekGlobal && notBranches && branch
global = stack.peek().and(notBranches).andWith(branch.id());
if (null != current) {
current.delRef();
current = null;
}
}
/**
* Enter a NEXT conditional branch. IMPORTANT: the preprocessor
* needs to call enterElse before evaluating the #elif's conditional
* expression, otherwise macros in the expression will be evaluated
* as if they were in the previous branch, which is wrong.
*
* @param bdd The presence condition of the branch.
*/
public void enterElif(BDD bdd) {
branch = bdd;
global = stack.peek().and(notBranches).andWith(branch.id());
if (null != current) {
current.delRef();
current = null;
}
}
/**
* Enter an else branch. Else is a branch whose local context is
* TRUE.
*/
public void enterElse() {
enter(B.one());
}
/**
* Pop the current presence condition off of the stack.
*/
public void pop() {
global.free();
branch.free();
notBranches.free();
global = stack.pop();
branch = stack.pop();
notBranches = stack.pop();
if (null != current) {
current.delRef();
current = null;
}
}
/**
* Get the parent presence condition, i.e. the presence condition
* containing the current one.
*
* @param The parent presence condition.
*/
public Context parent() {
return new Context(stack.peek().id());
}
/**
* Whether the current presence condition is true.
*
* @return true When the the current presence condition is true.
*/
public boolean isTrue() {
return global.isOne();
}
/**
* Whether the current presence condition is false
*
* @return true When the the current presence condition is false.
*/
public boolean isFalse() {
return global.isZero();
}
/**
* Get the current presence condition.
*
* @return The current presence condition.
*/
public Context reference() {
if (null == current) {
current = new Context(global.id());
}
current.addRef();
return current;
}
public boolean is(Context context) {
return global.equals(context.getBDD());
}
public Variables getVariableManager() {
return vars;
}
/**
* The nesting depth of presence conditions.
*
* @return The nesting depth.
*/
public int getDepth() {
return stack.size();
}
/**
* The BDD factory used to create BDDs. This is needed for directly
* manipulating BDDs outside of the ContextManager, because all BDDs
* that share variables need to be created from the same factory.
*
* @return The BDD factory.
*/
public BDDFactory getBDDFactory() {
return B;
}
/** A reference-counted context that automatically cleans up BDD when
* nothing references it anymore.
*/
public class Context {
private BDD bdd;
private int refs;
/** Creates a new Context out of the given bdd. Make sure the bdd
* is not shared by anyone else.
*/
public Context(BDD bdd) {
this.bdd = bdd;
this.refs = 1;
}
public Context(boolean value) {
this.bdd = value ? B.one() : B.zero();
this.refs = 1;
}
public boolean isTrue() {
return bdd.isOne();
}
public boolean isFalse() {
return bdd.isZero();
}
/** Return the negated context. */
public Context not() {
return new Context(bdd.not());
}
/** Return this context and c. Free any intermediate bdds. */
public Context and(Context c) {
return new Context(bdd.and(c.bdd));
}
/** Return this context and not c. Free any intermediate bdds. */
public Context andNot(Context c) {
Context newContext;
BDD notBDD;
notBDD = c.bdd.not();
newContext = new Context(bdd.and(notBDD));
notBDD.free();
return newContext;
}
/** Return this context or c. Free any intermediate bdds. */
public Context or(Context c) {
return new Context(bdd.or(c.bdd));
}
/** Restrict */
public Context restrict(Context c) {
return new Context(bdd.restrict(c.getBDD()));
}
/** Compare */
public boolean is(Context context) {
return is(context.getBDD());
}
/** Compare */
public boolean is(BDD bdd) {
return this.bdd.equals(bdd);
}
/**
*
*/
public boolean isMutuallyExclusive(Context context) {
Context and;
and = this.and(context);
if (and.isFalse()) {
and.delRef();
return true;
}
else {
and.delRef();
return false;
}
}
public Context addRef() {
if (refs > 0) {
refs++;
}
return this;
}
public void delRef() {
if (refs > 0) {
refs--;
if (0 == refs) {
bdd.free();
}
}
}
/**
* Get the raw BDD backing this presence condition.
*
* @return The raw BDD.
*/
public BDD getBDD() {
return bdd;
}
/**
* Print the BDD to a writer.
*
* @param writer The writer.
* @throws IOException Because it uses a Writer.
*/
public void print(Writer writer) throws IOException {
List allsat;
boolean firstTerm;
if (bdd.isOne()) {
writer.write("1");
return;
} else if (bdd.isZero()) {
writer.write("0");
return;
}
allsat = (List) bdd.allsat();
firstTerm = true;
for (Object o : allsat) {
byte[] sat;
boolean first;
if (! firstTerm) {
writer.write(" || ");
}
firstTerm = false;
sat = (byte[]) o;
first = true;
for (int i = 0; i < sat.length; i++) {
if (sat[i] >= 0 && ! first) {
writer.write(" && ");
}
switch (sat[i]) {
case 0:
writer.write("!");
case 1:
writer.write(vars.getName(i));
first = false;
break;
}
}
}
}
/** Output the context as a valid cpp conditional expression */
public String toString() {
StringWriter writer = new StringWriter();
try {
print(writer);
} catch (IOException e) {
// An inelegant way to sidestep not being able to throw an
// exception from the overridden toString method.
throw new RuntimeException();
}
return writer.toString();
}
}
/**
* Manages BDD variables and provides a string representation for
* BDD variables.
*/
public static class Variables {
/** The BDD factory */
private BDDFactory B;
/** The initial number of variables */
private int varNum;
/** The number of variables to add when varNum is topped */
private int extVarNum;
/** Hash from variable name to BDD variable index */
private Map<String, BDD> variables;
/** Map from BDD variable index to variable name */
private List<String> indices;
/**
* Create a new BDD variable manager.
*
* @param B The BDD factory.
*/
public Variables(BDDFactory B) {
this(B, 1023, 512);
}
/**
* Create a new BDD variable manager.
*
* @param B The BDD factory.
* @param varNum The initial number of BDD variables.
* @param extVarNum How much to increase the number of variables
* by each time the limit is reached.
*/
public Variables(BDDFactory B, int varNum, int extVarNum) {
this.B = B;
this.varNum = varNum;
this.extVarNum = extVarNum;
this.variables = new HashMap<String, BDD>();
this.indices = new ArrayList<String>();
B.setVarNum(this.varNum);
B.setMinFreeNodes(.40);
B.setMaxIncrease(500000);
}
/**
* Create a new BDD with the given variable name. If the variable
* does not exist yet, create it.
*
* @param str The name of the variable.
* @return A new BDD containing the variable.
*/
public BDD getVariable(String str) {
if (variables.containsKey(str)) {
return variables.get(str).id();
} else {
int newNum = indices.size();
BDD newBDD;
if (newNum > varNum - 1) {
varNum += extVarNum;
//System.err.println("INCREASE: " + varNum);
B.extVarNum(extVarNum);
}
newBDD = B.ithVar(newNum);
variables.put(str, newBDD);
indices.add(str);
return newBDD.id();
}
}
/**
* Map BDD variable number to name.
*
* @param i The variable number.
* @return The variable name.
*/
public String getName(int i) {
if (i < indices.size()) {
return indices.get(i);
}
else {
return null;
}
}
/**
* Determines whether the variable name exists.
*
* @param The variable name.
* @return true if it exists.
*/
public boolean hasVariable(String name) {
return variables.containsKey(name);
}
/**
* Create a string representation for the "is a macro defined"
* boolean variable, i.e. the "defined" operator. This variable is
* truly boolean variable, as opposed to the macro name itself,
* since a macro may have non-boolean values.
*
* @param The name of the macro.
* @return A string representation of the boolean variable.
*/
public String createDefinedVariable(String name) {
return "(defined " + name + ")";
}
/**
* Create a string representation for the "is a macro not defined"
* boolean variable
*
* @param The name of the macro.
* @return A string representation of the boolean variable.
*/
public String createNotDefinedVariable(String name) {
return "! " + createDefinedVariable(name);
}
/**
* Syntactic sugar for hasVariable(createDefinedVariable(name)).
*
* @param name The macro name.
* @return true If the variable exists.
*/
public boolean hasDefinedVariable(String name) {
return hasVariable(createDefinedVariable(name));
}
/**
* Syntactic sugar for getVariable(createDefinedVariable(name)).
*
* @param name The macro name.
* @return A new BDD containing the variable.
*/
public BDD getDefinedVariable(String name) {
return getVariable(createDefinedVariable(name));
}
}
}