/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify,
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
* OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package edu.mit.csail.sdg.alloy4compiler.translator;
import static edu.mit.csail.sdg.alloy4.Util.tail;
import static edu.mit.csail.sdg.alloy4compiler.ast.Sig.UNIV;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import kodkod.ast.BinaryExpression;
import kodkod.ast.Decls;
import kodkod.ast.ExprToIntCast;
import kodkod.ast.Expression;
import kodkod.ast.Formula;
import kodkod.ast.IntConstant;
import kodkod.ast.IntExpression;
import kodkod.ast.IntToExprCast;
import kodkod.ast.QuantifiedFormula;
import kodkod.ast.Relation;
import kodkod.ast.Variable;
import kodkod.ast.operator.ExprOperator;
import kodkod.engine.CapacityExceededException;
import kodkod.engine.fol2sat.HigherOrderDeclException;
import kodkod.instance.Tuple;
import kodkod.instance.TupleFactory;
import kodkod.instance.TupleSet;
import kodkod.util.ints.IntVector;
import edu.mit.csail.sdg.alloy4.A4Reporter;
import edu.mit.csail.sdg.alloy4.ConstList;
import edu.mit.csail.sdg.alloy4.ConstMap;
import edu.mit.csail.sdg.alloy4.Env;
import edu.mit.csail.sdg.alloy4.Err;
import edu.mit.csail.sdg.alloy4.ErrorFatal;
import edu.mit.csail.sdg.alloy4.ErrorSyntax;
import edu.mit.csail.sdg.alloy4.ErrorType;
import edu.mit.csail.sdg.alloy4.Pair;
import edu.mit.csail.sdg.alloy4.Pos;
import edu.mit.csail.sdg.alloy4.Util;
import edu.mit.csail.sdg.alloy4compiler.ast.Command;
import edu.mit.csail.sdg.alloy4compiler.ast.CommandScope;
import edu.mit.csail.sdg.alloy4compiler.ast.Decl;
import edu.mit.csail.sdg.alloy4compiler.ast.Expr;
import edu.mit.csail.sdg.alloy4compiler.ast.ExprBinary;
import edu.mit.csail.sdg.alloy4compiler.ast.ExprCall;
import edu.mit.csail.sdg.alloy4compiler.ast.ExprConstant;
import edu.mit.csail.sdg.alloy4compiler.ast.ExprHasName;
import edu.mit.csail.sdg.alloy4compiler.ast.ExprITE;
import edu.mit.csail.sdg.alloy4compiler.ast.ExprLet;
import edu.mit.csail.sdg.alloy4compiler.ast.ExprList;
import edu.mit.csail.sdg.alloy4compiler.ast.ExprQt;
import edu.mit.csail.sdg.alloy4compiler.ast.ExprUnary;
import edu.mit.csail.sdg.alloy4compiler.ast.ExprVar;
import edu.mit.csail.sdg.alloy4compiler.ast.Func;
import edu.mit.csail.sdg.alloy4compiler.ast.Sig;
import edu.mit.csail.sdg.alloy4compiler.ast.Type;
import edu.mit.csail.sdg.alloy4compiler.ast.VisitReturn;
import edu.mit.csail.sdg.alloy4compiler.ast.Sig.Field;
/** Translate an Alloy AST into Kodkod AST then attempt to solve it using Kodkod. */
public class TranslateAlloyToKodkod extends VisitReturn<Object> {
static int cnt = 0;
/** This is used to detect "function recursion" (which we currently do not allow);
* also, by knowing the current function name, we can provide a more meaningful name for skolem variables
*/
private final List<Func> current_function = new ArrayList<Func>();
/** This maps the current local variables (LET, QUANT, Function Param) to the actual Kodkod Expression/IntExpression/Formula. */
private Env<ExprVar,Object> env = new Env<ExprVar,Object>();
/** If frame!=null, it stores the scope, bounds, and other settings necessary for performing a solve. */
protected final A4Solution frame;
/** If frame==null, it stores the mapping from each Sig/Field/Skolem/Atom to its corresponding Kodkod expression. */
private final ConstMap<Expr,Expression> a2k;
/** If frame==null, it stores the mapping from each String literal to its corresponding Kodkod expression. */
private final ConstMap<String,Expression> s2k;
/** The current reporter. */
private A4Reporter rep;
/** If nonnull, it's the current command. */
private final Command cmd;
/** The bitwidth. */
private final int bitwidth;
/** The minimum allowed integer. */
private final int min;
/** The maximum allowed integer. */
private final int max;
/** The maximum allowed loop unrolling and recursion. */
private final int unrolls;
/** Construct a translator based on the given list of sigs and the given command.
* @param rep - if nonnull, it's the reporter that will receive diagnostics and progress reports
* @param opt - the solving options (must not be null)
* @param sigs - the list of sigs (must not be null, and must be a complete list)
* @param cmd - the command to solve (must not be null)
*/
private TranslateAlloyToKodkod (A4Reporter rep, A4Options opt, Iterable<Sig> sigs, Command cmd) throws Err {
this.unrolls = opt.unrolls;
this.rep = (rep != null) ? rep : A4Reporter.NOP;
this.cmd = cmd;
Pair<A4Solution, ScopeComputer> pair = ScopeComputer.compute(this.rep, opt, sigs, cmd);
this.frame = pair.a;
this.bitwidth = pair.a.getBitwidth();
this.min = pair.a.min();
this.max = pair.a.max();
this.a2k = null;
this.s2k = null;
BoundsComputer.compute(rep, frame, pair.b, sigs);
}
/** Construct a translator based on a already-fully-constructed association map.
* @param bitwidth - the integer bitwidth to use
* @param unrolls - the maximum number of loop unrolling and recursion allowed
* @param a2k - the mapping from Alloy sig/field/skolem/atom to the corresponding Kodkod expression
*/
private TranslateAlloyToKodkod (int bitwidth, int unrolls, Map<Expr,Expression> a2k, Map<String,Expression> s2k) throws Err {
this.unrolls = unrolls;
if (bitwidth<0) throw new ErrorSyntax("Cannot specify a bitwidth less than 0");
if (bitwidth>30) throw new ErrorSyntax("Cannot specify a bitwidth greater than 30");
this.rep = A4Reporter.NOP;
this.cmd = null;
this.frame = null;
this.bitwidth = bitwidth;
this.max = Util.max(bitwidth);
this.min = Util.min(bitwidth);
this.a2k = ConstMap.make(a2k);
this.s2k = ConstMap.make(s2k);
}
/** Contructor for usage from subclasses. */
protected TranslateAlloyToKodkod(A4Reporter rep, A4Options opt, A4Solution frame, Command cmd) {
this.unrolls = opt.unrolls;
this.frame = frame;
this.min = frame.min();
this.max = frame.max();
this.a2k = null;
this.s2k = null;
this.cmd = cmd;
this.bitwidth = frame.getBitwidth();
this.rep = (rep != null) ? rep : A4Reporter.NOP;
}
/** Associate the given formula with the given expression, then return the formula as-is. */
private Formula k2pos(Formula f, Expr e) throws Err {
if (k2pos_enabled) if (frame!=null) frame.k2pos(f, e);
return f;
}
private boolean k2pos_enabled = true;
/** Returns the expression corresponding to the given sig. */
private Expression a2k(Sig x) throws Err { if (a2k!=null) return a2k.get(x); else return frame.a2k(x); }
/** Returns the expression corresponding to the given field. */
private Expression a2k(Field x) throws Err { if (a2k!=null) return a2k.get(x); else return frame.a2k(x); }
/** Returns the expression corresponding to the given skolem/atom. */
private Expression a2k(ExprVar x) throws Err { if (a2k!=null) return a2k.get(x); else return frame.a2k(x); }
/** Returns the expression corresponding to the given string literal. */
private Expression s2k(String x) throws Err { if (s2k!=null) return s2k.get(x); else return frame.a2k(x); }
//==============================================================================================================//
/** Stores the list of "totalOrder predicates" that we constructed. */
private final List<Relation> totalOrderPredicates = new ArrayList<Relation>();
/** Conjoin the constraints for "field declarations" and "fact" paragraphs */
protected void makeFacts(Expr facts) throws Err {
rep.debug("Generating facts...\n");
// convert into a form that hopefully gives better unsat core
facts = (Expr) (new ConvToConjunction()).visitThis(facts);
// add the field facts and appended facts
for(Sig s: frame.getAllReachableSigs()) {
for(Decl d: s.getFieldDecls()) {
k2pos_enabled = false;
for(ExprHasName n: d.names) {
Field f = (Field)n;
Expr form = s.decl.get().join(f).in(d.expr);
form = s.isOne==null ? form.forAll(s.decl) : ExprLet.make(null, (ExprVar)(s.decl.get()), s, form);
frame.addFormula(cform(form), f);
// Given the above, we can be sure that every column is well-bounded (except possibly the first column).
// Thus, we need to add a bound that the first column is a subset of s.
if (s.isOne==null) {
Expression sr = a2k(s), fr = a2k(f);
for(int i=f.type().arity(); i>1; i--) fr=fr.join(Relation.UNIV);
frame.addFormula(fr.in(sr), f);
}
}
if (s.isOne==null && d.disjoint2!=null) for(ExprHasName f: d.names) {
Decl that = s.oneOf("that");
Expr formula = s.decl.get().equal(that.get()).not().implies(s.decl.get().join(f).intersect(that.get().join(f)).no());
frame.addFormula(cform(formula.forAll(that).forAll(s.decl)), d.disjoint2);
}
if (d.names.size()>1 && d.disjoint!=null) { frame.addFormula(cform(ExprList.makeDISJOINT(d.disjoint, null, d.names)), d.disjoint); }
}
k2pos_enabled = true;
for(Expr f: s.getFacts()) {
Expr form = s.isOne==null ? f.forAll(s.decl) : ExprLet.make(null, (ExprVar)(s.decl.get()), s, f);
frame.addFormula(cform(form), f);
}
}
k2pos_enabled = true;
recursiveAddFormula(facts);
}
/** Break up x into conjuncts then add them each as a fact. */
private void recursiveAddFormula(Expr x) throws Err {
if (x instanceof ExprList && ((ExprList)x).op==ExprList.Op.AND) {
for(Expr e: ((ExprList)x).args) recursiveAddFormula(e);
} else {
frame.addFormula(cform(x), x);
}
}
//==============================================================================================================//
private static final class GreedySimulator extends Simplifier {
private List<Relation> totalOrderPredicates = null;
private Iterable<Sig> allSigs = null;
private ConstList<Sig> growableSigs = null;
private A4Solution partial = null;
public GreedySimulator() { }
private TupleSet convert(TupleFactory factory, Expr f) throws Err {
TupleSet old = ((A4TupleSet) (partial.eval(f))).debugGetKodkodTupleset();
TupleSet ans = factory.noneOf(old.arity());
for(Tuple oldT: old) {
Tuple newT = null;
for(int i=0; i<oldT.arity(); i++) {
if (newT==null) newT=factory.tuple(oldT.atom(i)); else newT=newT.product(factory.tuple(oldT.atom(i)));
}
ans.add(newT);
}
return ans;
}
@Override public boolean simplify(A4Reporter rep, A4Solution sol, List<Formula> unused) throws Err {
TupleFactory factory = sol.getFactory();
TupleSet oldUniv = convert(factory, Sig.UNIV);
Set<Object> oldAtoms = new HashSet<Object>(); for(Tuple t: oldUniv) oldAtoms.add(t.atom(0));
for(Sig s: allSigs) {
// The case below is STRICTLY an optimization; the entire statement can be removed without affecting correctness
if (s.isOne!=null && s.getFields().size()==2)
for(int i=0; i+3<totalOrderPredicates.size(); i=i+4)
if (totalOrderPredicates.get(i+1)==right(sol.a2k(s.getFields().get(0))) && totalOrderPredicates.get(i+3)==right(sol.a2k(s.getFields().get(1)))) {
TupleSet allelem = sol.query(true, totalOrderPredicates.get(i), true);
if (allelem.size()==0) continue;
Tuple first=null, prev=null; TupleSet next=factory.noneOf(2);
for(Tuple t:allelem) {
if (prev==null) first=t; else next.add(prev.product(t));
prev=t;
}
try {
sol.shrink(totalOrderPredicates.get(i+1), factory.range(first,first), factory.range(first,first));
sol.shrink(totalOrderPredicates.get(i+2), factory.range(prev,prev), factory.range(prev,prev));
sol.shrink(totalOrderPredicates.get(i+3), next, next);
} catch(Throwable ex) {
// Error here is not fatal
}
}
// The case above is STRICTLY an optimization; the entire statement can be removed without affecting correctness
for(Field f: s.getFields()) {
Expression rel = sol.a2k(f);
if (s.isOne!=null) {
rel = right(rel);
if (!(rel instanceof Relation)) continue;
// Retrieve the old value from the previous solution, and convert it to the new unverse.
// This should always work since the new universe is not yet solved, and so it should have all possible atoms.
TupleSet newLower = convert(factory, s.join(f)), newUpper = newLower.clone();
// Bind the partial instance
for(Tuple t: sol.query(false, rel, false)) for(int i=0; i<t.arity(); i++) if (!oldAtoms.contains(t.atom(i))) { newLower.add(t); break; }
for(Tuple t: sol.query(true, rel, false)) for(int i=0; i<t.arity(); i++) if (!oldAtoms.contains(t.atom(i))) { newUpper.add(t); break; }
sol.shrink((Relation)rel, newLower, newUpper);
} else {
if (!(rel instanceof Relation)) continue;
// Retrieve the old value from the previous solution, and convert it to the new unverse.
// This should always work since the new universe is not yet solved, and so it should have all possible atoms.
TupleSet newLower = convert(factory, f), newUpper = newLower.clone();
// Bind the partial instance
for(Tuple t: sol.query(false, rel, false)) for(int i=0; i<t.arity(); i++) if (!oldAtoms.contains(t.atom(i))) { newLower.add(t); break; }
for(Tuple t: sol.query(true, rel, false)) for(int i=0; i<t.arity(); i++) if (!oldAtoms.contains(t.atom(i))) { newUpper.add(t); break; }
sol.shrink((Relation)rel, newLower, newUpper);
}
}
}
return true;
}
}
static ErrorType rethrow(CapacityExceededException ex) {
IntVector vec = ex.dims();
return new ErrorType(
"Translation capacity exceeded.\n"
+ "In this scope, universe contains " + vec.get(0) + " atoms\n"
+ "and relations of arity " + vec.size() + " cannot be represented.\n"
+ "Visit http://alloy.mit.edu/ for advice on refactoring.");
}
private static A4Solution execute_greedyCommand(A4Reporter rep, Iterable<Sig> sigs, Command usercommand, A4Options opt) throws Exception {
// FIXTHIS: if the next command has a "smaller scope" than the last command, we would get a Kodkod exception...
// FIXTHIS: if the solver is "toCNF" or "toKodkod" then this method will throw an Exception...
// FIXTHIS: does solution enumeration still work when we're doing a greedy solve?
TranslateAlloyToKodkod tr = null;
try {
long start = System.currentTimeMillis();
GreedySimulator sim = new GreedySimulator();
sim.allSigs = sigs;
sim.partial = null;
A4Reporter rep2 = new A4Reporter(rep) {
private boolean first = true;
public void translate(String solver, int bitwidth, int maxseq, int skolemDepth, int symmetry) { if (first) super.translate(solver, bitwidth, maxseq, skolemDepth, symmetry); first=false; }
public void resultSAT(Object command, long solvingTime, Object solution) { }
public void resultUNSAT(Object command, long solvingTime, Object solution) { }
};
// Form the list of commands
List<Command> commands = new ArrayList<Command>();
while(usercommand!=null) { commands.add(usercommand); usercommand = usercommand.parent; }
// For each command...
A4Solution sol = null;
for(int i=commands.size()-1; i>=0; i--) {
Command cmd = commands.get(i);
sim.growableSigs = cmd.getGrowableSigs();
while(cmd != null) {
rep.debug(cmd.scope.toString());
usercommand = cmd;
tr = new TranslateAlloyToKodkod(rep2, opt, sigs, cmd);
tr.makeFacts(cmd.formula);
sim.totalOrderPredicates = tr.totalOrderPredicates;
sol = tr.frame.solve(rep2, cmd, sim.partial==null || cmd.check ? new Simplifier() : sim, false);
if (!sol.satisfiable() && !cmd.check) {
start = System.currentTimeMillis() - start;
if (sim.partial==null) { rep.resultUNSAT(cmd, start, sol); return sol; } else { rep.resultSAT(cmd, start, sim.partial); return sim.partial; }
}
if (sol.satisfiable() && cmd.check) {
start = System.currentTimeMillis() - start;
rep.resultSAT(cmd, start, sol); return sol;
}
sim.partial = sol;
if (sim.growableSigs.isEmpty()) break;
for(Sig s: sim.growableSigs) {
CommandScope sc = cmd.getScope(s);
if (sc.increment > sc.endingScope - sc.startingScope) {cmd=null; break;}
cmd = cmd.change(s, sc.isExact, sc.startingScope+sc.increment, sc.endingScope, sc.increment);
}
}
}
if (sol.satisfiable()) rep.resultSAT(usercommand, System.currentTimeMillis()-start, sol); else rep.resultUNSAT(usercommand, System.currentTimeMillis()-start, sol);
return sol;
} catch(CapacityExceededException ex) {
throw rethrow(ex);
} catch(HigherOrderDeclException ex) {
Pos p = tr!=null ? tr.frame.kv2typepos(ex.decl().variable()).b : Pos.UNKNOWN;
throw new ErrorType(p, "Analysis cannot be performed since it requires higher-order quantification that could not be skolemized.");
}
}
/** Based on the specified "options", execute one command and return the resulting A4Solution object.
*
* @param rep - if nonnull, we'll send compilation diagnostic messages to it
* @param sigs - the list of sigs; this list must be complete
* @param cmd - the Command to execute
* @param opt - the set of options guiding the execution of the command
*
* @return null if the user chose "save to FILE" as the SAT solver,
* and nonnull if the solver finishes the entire solving and is either satisfiable or unsatisfiable.
* <p> If the return value X is satisfiable, you can call X.next() to get the next satisfying solution X2;
* and you can call X2.next() to get the next satisfying solution X3... until you get an unsatisfying solution.
*/
public static A4Solution execute_command (A4Reporter rep, Iterable<Sig> sigs, Command cmd, A4Options opt) throws Err {
if (rep==null) rep = A4Reporter.NOP;
TranslateAlloyToKodkod tr = null;
try {
if (cmd.parent!=null || !cmd.getGrowableSigs().isEmpty()) return execute_greedyCommand(rep, sigs, cmd, opt);
tr = new TranslateAlloyToKodkod(rep, opt, sigs, cmd);
tr.makeFacts(cmd.formula);
return tr.frame.solve(rep, cmd, new Simplifier(), false);
} catch(UnsatisfiedLinkError ex) {
throw new ErrorFatal("The required JNI library cannot be found: "+ex.toString().trim(), ex);
} catch(CapacityExceededException ex) {
throw rethrow(ex);
} catch(HigherOrderDeclException ex) {
Pos p = tr!=null ? tr.frame.kv2typepos(ex.decl().variable()).b : Pos.UNKNOWN;
throw new ErrorType(p, "Analysis cannot be performed since it requires higher-order quantification that could not be skolemized.");
} catch(Throwable ex) {
if (ex instanceof Err) throw (Err)ex; else throw new ErrorFatal("Unknown exception occurred: "+ex, ex);
}
}
/** Based on the specified "options", execute one command and return the resulting A4Solution object.
*
* <p> Note: it will first test whether the model fits one of the model from the "Software Abstractions" book;
* if so, it will use the exact instance that was in the book.
*
* @param rep - if nonnull, we'll send compilation diagnostic messages to it
* @param sigs - the list of sigs; this list must be complete
* @param cmd - the Command to execute
* @param opt - the set of options guiding the execution of the command
*
* @return null if the user chose "save to FILE" as the SAT solver,
* and nonnull if the solver finishes the entire solving and is either satisfiable or unsatisfiable.
* <p> If the return value X is satisfiable, you can call X.next() to get the next satisfying solution X2;
* and you can call X2.next() to get the next satisfying solution X3... until you get an unsatisfying solution.
*/
public static A4Solution execute_commandFromBook (A4Reporter rep, Iterable<Sig> sigs, Command cmd, A4Options opt) throws Err {
if (rep==null) rep = A4Reporter.NOP;
TranslateAlloyToKodkod tr = null;
try {
if (cmd.parent!=null || !cmd.getGrowableSigs().isEmpty()) return execute_greedyCommand(rep, sigs, cmd, opt);
tr = new TranslateAlloyToKodkod(rep, opt, sigs, cmd);
tr.makeFacts(cmd.formula);
return tr.frame.solve(rep, cmd, new Simplifier(), true);
} catch(UnsatisfiedLinkError ex) {
throw new ErrorFatal("The required JNI library cannot be found: "+ex.toString().trim(), ex);
} catch(CapacityExceededException ex) {
throw rethrow(ex);
} catch(HigherOrderDeclException ex) {
Pos p = tr!=null ? tr.frame.kv2typepos(ex.decl().variable()).b : Pos.UNKNOWN;
throw new ErrorType(p, "Analysis cannot be performed since it requires higher-order quantification that could not be skolemized.");
} catch(Throwable ex) {
if (ex instanceof Err) throw (Err)ex; else throw new ErrorFatal("Unknown exception occurred: "+ex, ex);
}
}
/** Translate the Alloy expression into an equivalent Kodkod Expression or IntExpression or Formula object.
* @param sol - an existing satisfiable A4Solution object
* @param expr - this is the Alloy expression we want to translate
*/
public static Object alloy2kodkod(A4Solution sol, Expr expr) throws Err {
if (expr.ambiguous && !expr.errors.isEmpty()) expr = expr.resolve(expr.type(), null);
if (!expr.errors.isEmpty()) throw expr.errors.pick();
TranslateAlloyToKodkod tr = new TranslateAlloyToKodkod(sol.getBitwidth(), sol.unrolls(), sol.a2k(), sol.s2k());
Object ans;
try {
ans = tr.visitThis(expr);
} catch(UnsatisfiedLinkError ex) {
throw new ErrorFatal("The required JNI library cannot be found: "+ex.toString().trim());
} catch(CapacityExceededException ex) {
throw rethrow(ex);
} catch(HigherOrderDeclException ex) {
throw new ErrorType("Analysis cannot be performed since it requires higher-order quantification that could not be skolemized.");
} catch(Throwable ex) {
if (ex instanceof Err) throw (Err)ex;
throw new ErrorFatal("Unknown exception occurred: "+ex, ex);
}
if ((ans instanceof IntExpression) || (ans instanceof Formula) || (ans instanceof Expression)) return ans;
throw new ErrorFatal("Unknown internal error encountered in the evaluator.");
}
//==============================================================================================================//
/** Convenience method that evalutes x and casts the result to be a Kodkod Formula.
* @return the formula - if x evaluates to a Formula
* @throws ErrorFatal - if x does not evaluate to a Formula
*/
private Formula cform(Expr x) throws Err {
if (!x.errors.isEmpty()) throw x.errors.pick();
Object y=visitThis(x);
if (y instanceof Formula) return (Formula)y;
throw new ErrorFatal(x.span(), "This should have been a formula.\nInstead it is "+y);
}
/** Convenience method that evalutes x and cast the result to be a Kodkod IntExpression.
* @return the integer expression - if x evaluates to an IntExpression
* @throws ErrorFatal - if x does not evaluate to an IntExpression
*/
private IntExpression cint(Expr x) throws Err {
if (!x.errors.isEmpty()) throw x.errors.pick();
return toInt(x, visitThis(x));
}
private IntExpression toInt(Expr x, Object y) throws Err, ErrorFatal {
// simplify: if y is int[Int[sth]] then return sth
if (y instanceof ExprToIntCast) {
ExprToIntCast y2 = (ExprToIntCast) y;
if (y2.expression() instanceof IntToExprCast)
return ((IntToExprCast)y2.expression()).intExpr();
}
// simplify: if y is Int[sth], then return sth
if (y instanceof IntToExprCast)
return ((IntToExprCast) y).intExpr();
if (y instanceof IntExpression)
return (IntExpression)y;
//[AM]: maybe this conversion should be removed
if (y instanceof Expression) return ((Expression) y).sum();
throw new ErrorFatal(x.span(), "This should have been an integer expression.\nInstead it is "+y);
}
/** Convenience method that evaluаtes x and cast the result to be a Kodkod Expression.
* @return the expression - if x evaluates to an Expression
* @throws ErrorFatal - if x does not evaluate to an Expression
*/
private Expression cset(Expr x) throws Err {
if (!x.errors.isEmpty()) throw x.errors.pick();
return toSet(x, visitThis(x));
}
private Expression toSet(Expr x, Object y) throws Err, ErrorFatal {
if (y instanceof Expression) return (Expression)y;
if (y instanceof IntExpression) return ((IntExpression) y).toExpression();
throw new ErrorFatal(x.span(), "This should have been a set or a relation.\nInstead it is "+y);
}
//==============================================================================================================//
/** Given a variable name "name", prepend the current function name to form a meaningful "skolem name".
* (Note: this function does NOT, and need NOT guarantee that the name it generates is unique)
*/
private String skolem(String name) {
if (current_function.size()==0) {
if (cmd!=null && cmd.label.length()>0 && cmd.label.indexOf('$')<0) return cmd.label+"_"+name; else return name;
}
Func last=current_function.get(current_function.size()-1);
String funcname=tail(last.label);
if (funcname.indexOf('$')<0) return funcname+"_"+name; else return name;
}
//==============================================================================================================//
/** If x = SOMETHING->RELATION where SOMETHING.arity==1, then return the RELATION, else return null. */
private static Relation right(Expression x) {
if (!(x instanceof BinaryExpression)) return null;
BinaryExpression bin = (BinaryExpression)x;
if (bin.op() != ExprOperator.PRODUCT) return null;
if (bin.left().arity()==1 && bin.right() instanceof Relation) return (Relation)(bin.right()); else return null;
}
//==============================================================================================================//
/*============================*/
/* Evaluates an ExprITE node. */
/*============================*/
/** {@inheritDoc} */
@Override public Object visit(ExprITE x) throws Err {
Formula c = cform(x.cond);
Object l = visitThis(x.left);
if (l instanceof Formula) {
Formula c1 = c.implies((Formula)l);
Formula c2 = c.not().implies(cform(x.right));
return k2pos(c1.and(c2), x);
}
if (l instanceof Expression) {
return c.thenElse((Expression)l, cset(x.right));
}
return c.thenElse((IntExpression)l, cint(x.right));
}
/*============================*/
/* Evaluates an ExprLet node. */
/*============================*/
/** {@inheritDoc} */
@Override public Object visit(ExprLet x) throws Err {
env.put(x.var, visitThis(x.expr));
Object ans = visitThis(x.sub);
env.remove(x.var);
return ans;
}
/*=================================*/
/* Evaluates an ExprConstant node. */
/*=================================*/
/** {@inheritDoc} */
@Override public Object visit(ExprConstant x) throws Err {
switch(x.op) {
case MIN: return IntConstant.constant(min); //TODO
case MAX: return IntConstant.constant(max); //TODO
case NEXT: return A4Solution.KK_NEXT;
case TRUE: return Formula.TRUE;
case FALSE: return Formula.FALSE;
case EMPTYNESS: return Expression.NONE;
case IDEN: return Expression.IDEN.intersection(a2k(UNIV).product(Expression.UNIV));
case STRING:
Expression ans = s2k(x.string);
if (ans==null) throw new ErrorFatal(x.pos, "String literal "+x+" does not exist in this instance.\n");
return ans;
case NUMBER:
int n=x.num();
//[am] const
// if (n<min) throw new ErrorType(x.pos, "Current bitwidth is set to "+bitwidth+", thus this integer constant "+n+" is smaller than the minimum integer "+min);
// if (n>max) throw new ErrorType(x.pos, "Current bitwidth is set to "+bitwidth+", thus this integer constant "+n+" is bigger than the maximum integer "+max);
return IntConstant.constant(n).toExpression();
}
throw new ErrorFatal(x.pos, "Unsupported operator ("+x.op+") encountered during ExprConstant.accept()");
}
/*==============================*/
/* Evaluates an ExprUnary node. */
/*==============================*/
/** {@inheritDoc} */
@Override public Object visit(ExprUnary x) throws Err {
switch(x.op) {
case EXACTLYOF: case SOMEOF: case LONEOF: case ONEOF: case SETOF: return cset(x.sub);
case NOOP: return visitThis(x.sub);
case NOT: return k2pos( cform(x.sub).not() , x );
case SOME: return k2pos( cset(x.sub).some() , x);
case LONE: return k2pos( cset(x.sub).lone() , x);
case ONE: return k2pos( cset(x.sub).one() , x);
case NO: return k2pos( cset(x.sub).no() , x);
case TRANSPOSE: return cset(x.sub).transpose();
case CARDINALITY: return cset(x.sub).count();
case CAST2SIGINT: return cint(x.sub).toExpression();
case CAST2INT: return sum(cset(x.sub));
case RCLOSURE:
Expression iden=Expression.IDEN.intersection(a2k(UNIV).product(Relation.UNIV));
return cset(x.sub).closure().union(iden);
case CLOSURE: return cset(x.sub).closure();
}
throw new ErrorFatal(x.pos, "Unsupported operator ("+x.op+") encountered during ExprUnary.visit()");
}
/** Performs int[x]; contains an efficiency shortcut that simplifies int[Int[x]] to x. */
private IntExpression sum(Expression x) {
if (x instanceof IntToExprCast) return ((IntToExprCast)x).intExpr(); else return x.sum();
}
/*============================*/
/* Evaluates an ExprVar node. */
/*============================*/
/** {@inheritDoc} */
@Override public Object visit(ExprVar x) throws Err {
Object ans=env.get(x);
if (ans==null) ans=a2k(x);
if (ans==null) throw new ErrorFatal(x.pos, "Variable \""+x+"\" is not bound to a legal value during translation.\n");
return ans;
}
/*=========================*/
/* Evaluates a Field node. */
/*=========================*/
/** {@inheritDoc} */
@Override public Object visit(Field x) throws Err {
Expression ans = a2k(x);
if (ans==null) throw new ErrorFatal(x.pos, "Field \""+x+"\" is not bound to a legal value during translation.\n");
return ans;
}
/*=======================*/
/* Evaluates a Sig node. */
/*=======================*/
/** {@inheritDoc} */
@Override public Object visit(Sig x) throws Err {
Expression ans = a2k(x);
if (ans==null)
throw new ErrorFatal(x.pos, "Sig \""+x+"\" is not bound to a legal value during translation.\n");
return ans;
}
/*=============================*/
/* Evaluates an ExprCall node. */
/*=============================*/
/** Caches parameter-less functions to a Kodkod Expression, Kodkod IntExpression, or Kodkod Formula. */
private final Map<Func,Object> cacheForConstants = new IdentityHashMap<Func,Object>();
/** {@inheritDoc} */
@Override public Object visit(ExprCall x) throws Err {
final Func f = x.fun;
final Object candidate = f.count()==0 ? cacheForConstants.get(f) : null;
if (candidate!=null) return candidate;
final Expr body = f.getBody();
if (body.type().arity()<0 || body.type().arity()!=f.returnDecl.type().arity()) throw new ErrorType(body.span(), "Function return value not fully resolved.");
final int n = f.count();
int maxRecursion = unrolls;
for(Func ff:current_function) if (ff==f) {
if (maxRecursion<0) {
throw new ErrorSyntax(x.span(), ""+f+" cannot call itself recursively!");
}
if (maxRecursion==0) {
Type t = f.returnDecl.type();
if (t.is_bool) return Formula.FALSE;
if (t.is_int()) return IntConstant.constant(0);
int i = t.arity();
Expression ans = Expression.NONE;
while(i>1) { ans = ans.product(Expression.NONE); i--; }
return ans;
}
maxRecursion--;
}
Env<ExprVar,Object> newenv = new Env<ExprVar,Object>();
for(int i=0; i<n; i++) newenv.put(f.get(i), cset(x.args.get(i)));
Env<ExprVar,Object> oldenv = env;
env = newenv;
current_function.add(f);
Object ans = visitThis(body);
env = oldenv;
current_function.remove(current_function.size()-1);
if (ans instanceof Formula) k2pos((Formula)ans, x);
if (f.count()==0) cacheForConstants.put(f, ans);
return ans;
}
/*================================*/
/* Evaluates an ExprList node. */
/*================================*/
/** Helper method that merge a list of conjuncts or disjoints while minimizing the AST depth (external caller should use i==1) */
private Formula getSingleFormula(boolean isConjunct, int i, List<Expr> formulas) throws Err {
// We actually build a "binary heap" where node X's two children are node 2X and node 2X+1
int n = formulas.size();
if (n==0) return isConjunct ? Formula.TRUE : Formula.FALSE;
Formula me = cform(formulas.get(i-1)), other;
int child1=i+i, child2=child1+1;
if (child1<i || child1>n) return me;
other = getSingleFormula(isConjunct, child1, formulas); if (isConjunct) me=me.and(other); else me=me.or(other);
if (child2<1 || child2>n) return me;
other = getSingleFormula(isConjunct, child2, formulas); if (isConjunct) me=me.and(other); else me=me.or(other);
return me;
}
/** {@inheritDoc} */
@Override public Object visit(ExprList x) throws Err {
if (x.op == ExprList.Op.AND || x.op == ExprList.Op.OR) {
if (x.args.size()==0) return (x.op==ExprList.Op.AND) ? Formula.TRUE : Formula.FALSE;
Formula answer = getSingleFormula(x.op==ExprList.Op.AND, 1, x.args);
return k2pos(answer, x);
}
if (x.op == ExprList.Op.TOTALORDER) {
Expression elem = cset(x.args.get(0)), first = cset(x.args.get(1)), next = cset(x.args.get(2));
if (elem instanceof Relation && first instanceof Relation && next instanceof Relation) {
Relation lst = frame.addRel("", null, frame.query(true, (Relation)elem, false));
totalOrderPredicates.add((Relation)elem); totalOrderPredicates.add((Relation)first); totalOrderPredicates.add(lst); totalOrderPredicates.add((Relation)next);
return k2pos(((Relation)next).totalOrder((Relation)elem, (Relation)first, lst), x);
}
Formula f1 = elem.in(first.join(next.reflexiveClosure())); // every element is in the total order
Formula f2 = next.join(first).no(); // first element has no predecessor
Variable e = Variable.unary("v" + Integer.toString(cnt++));
Formula f3 = e.eq(first).or(next.join(e).one()); // each element (except the first) has one predecessor
Formula f4 = e.eq(elem.difference(next.join(elem))).or(e.join(next).one()); // each element (except the last) has one successor
Formula f5 = e.in(e.join(next.closure())).not(); // there are no cycles
return k2pos(f3.and(f4).and(f5).forAll(e.oneOf(elem)).and(f1).and(f2), x);
}
// This says no(a&b) and no((a+b)&c) and no((a+b+c)&d)...
// Emperically this seems to be more efficient than "no(a&b) and no(a&c) and no(b&c)"
Formula answer = null;
Expression a = null;
for(Expr arg:x.args) {
Expression b=cset(arg);
if (a==null) {a=b;continue;}
if (answer==null) answer=a.intersection(b).no(); else answer=a.intersection(b).no().and(answer);
a=a.union(b);
}
if (answer!=null) return k2pos(answer, x); else return Formula.TRUE;
}
/*===============================*/
/* Evaluates an ExprBinary node. */
/*===============================*/
/** {@inheritDoc} */
@Override public Object visit(ExprBinary x) throws Err {
Expr a=x.left, b=x.right;
Expression s, s2, eL, eR; IntExpression i; Formula f; Object objL, objR;
switch(x.op) {
case IMPLIES: f=cform(a).not().or(cform(b)); return k2pos(f,x);
case IN: return k2pos(isIn(cset(a),b), x);
case NOT_IN: return k2pos(isIn(cset(a),b).not(), x);
case LT: i=cint(a); f=i.lt(cint(b)); return k2pos(f,x);
case LTE: i=cint(a); f=i.lte(cint(b)); return k2pos(f,x);
case GT: i=cint(a); f=i.gt(cint(b)); return k2pos(f,x);
case GTE: i=cint(a); f=i.gte(cint(b)); return k2pos(f,x);
case NOT_LT: i=cint(a); f=i.lt(cint(b)).not(); return k2pos(f,x);
case NOT_LTE: i=cint(a); f=i.lte(cint(b)).not(); return k2pos(f,x);
case NOT_GT: i=cint(a); f=i.gt(cint(b)).not(); return k2pos(f,x);
case NOT_GTE: i=cint(a); f=i.gte(cint(b)).not(); return k2pos(f,x);
case AND: f=cform(a); f=f.and(cform(b)); return k2pos(f,x);
case OR: f=cform(a); f=f.or(cform(b)); return k2pos(f,x);
case IFF: f=cform(a); f=f.iff(cform(b)); return k2pos(f,x);
case PLUSPLUS: s=cset(a); return s.override(cset(b));
case MUL: i=cint(a); return i.multiply(cint(b));
case DIV: i=cint(a); return i.divide(cint(b));
case REM: i=cint(a); return i.modulo(cint(b));
case SHL: i=cint(a); return i.shl(cint(b));
case SHR: i=cint(a); return i.shr(cint(b));
case SHA: i=cint(a); return i.sha(cint(b));
case PLUS:
return cset(a).union(cset(b));
//[AM]
// obj = visitThis(a);
// if (obj instanceof IntExpression) { i=(IntExpression)obj; return i.plus(cint(b)); }
// s = (Expression)obj; return s.union(cset(b));
case IPLUS:
return cint(a).plus(cint(b));
case MINUS:
// Special exception to allow "0-8" to not throw an exception, where 7 is the maximum allowed integer (when bitwidth==4)
// (likewise, when bitwidth==5, then +15 is the maximum allowed integer, and we want to allow 0-16 without throwing an exception)
if (a instanceof ExprConstant && ((ExprConstant)a).op==ExprConstant.Op.NUMBER && ((ExprConstant)a).num()==0)
if (b instanceof ExprConstant && ((ExprConstant)b).op==ExprConstant.Op.NUMBER && ((ExprConstant)b).num()==max+1)
return IntConstant.constant(min);
return cset(a).difference(cset(b));
//[AM]
// obj=visitThis(a);
// if (obj instanceof IntExpression) { i=(IntExpression)obj; return i.minus(cint(b));}
// s=(Expression)obj; return s.difference(cset(b));
case IMINUS:
return cint(a).minus(cint(b));
case INTERSECT:
s=cset(a); return s.intersection(cset(b));
case ANY_ARROW_SOME: case ANY_ARROW_ONE: case ANY_ARROW_LONE:
case SOME_ARROW_ANY: case SOME_ARROW_SOME: case SOME_ARROW_ONE: case SOME_ARROW_LONE:
case ONE_ARROW_ANY: case ONE_ARROW_SOME: case ONE_ARROW_ONE: case ONE_ARROW_LONE:
case LONE_ARROW_ANY: case LONE_ARROW_SOME: case LONE_ARROW_ONE: case LONE_ARROW_LONE:
case ISSEQ_ARROW_LONE:
case ARROW:
s=cset(a); return s.product(cset(b));
case JOIN:
a=a.deNOP(); s=cset(a); s2=cset(b);
if (a instanceof Sig && ((Sig)a).isOne!=null && s2 instanceof BinaryExpression) {
BinaryExpression bin = (BinaryExpression)s2;
if (bin.op()==ExprOperator.PRODUCT && bin.left()==s) return bin.right();
}
return s.join(s2);
case EQUALS:
objL = visitThis(a);
objR = visitThis(b);
eL = toSet(a, objL);
eR = toSet(b, objR);
if (eL instanceof IntToExprCast && eR instanceof IntToExprCast)
f = ((IntToExprCast) eL).intExpr().eq(((IntToExprCast) eR).intExpr());
else
f = eL.eq(eR);
return k2pos(f, x);
case NOT_EQUALS:
objL = visitThis(a);
objR = visitThis(b);
eL = toSet(a, objL);
eR = toSet(b, objR);
if (eL instanceof IntToExprCast && eR instanceof IntToExprCast)
f = ((IntToExprCast) eL).intExpr().eq(((IntToExprCast) eR).intExpr()).not();
else
f = eL.eq(eR).not();
return k2pos(f, x);
case DOMAIN:
s=cset(a);
s2=cset(b);
for(int j=s2.arity(); j>1; j--) s=s.product(Expression.UNIV);
return s.intersection(s2);
case RANGE:
s=cset(a);
s2=cset(b);
for(int j=s.arity(); j>1; j--) s2=Expression.UNIV.product(s2);
return s.intersection(s2);
}
throw new ErrorFatal(x.pos, "Unsupported operator ("+x.op+") encountered during ExprBinary.accept()");
}
/** Helper method that translates the formula "a in b" into a Kodkod formula. */
private Formula isIn(Expression a, Expr right) throws Err {
Expression b;
if (right instanceof ExprBinary && right.mult!=0 && ((ExprBinary)right).op.isArrow) {
// Handles possible "binary" or higher-arity multiplicity
return isInBinary(a, (ExprBinary)right);
}
switch(right.mult()) {
case EXACTLYOF: b=cset(right); return a.eq(b);
case ONEOF: b=cset(right); return a.one().and(a.in(b));
case LONEOF: b=cset(right); return a.lone().and(a.in(b));
case SOMEOF: b=cset(right); return a.some().and(a.in(b));
default: b=cset(right); return a.in(b);
}
}
//[AM]
private static boolean am = true;
/** Helper method that translates the formula "r in (a ?->? b)" into a Kodkod formula. */
private Formula isInBinary(Expression r, ExprBinary ab) throws Err {
final Expression a=cset(ab.left), b=cset(ab.right);
Decls d=null, d2=null;
Formula ans1, ans2;
// "R in A ->op B" means for each tuple a in A, there are "op" tuples in r that begins with a.
Expression atuple=null, ar=r;
for(int i=a.arity(); i>0; i--) {
Variable v=Variable.unary("v" + Integer.toString(cnt++));
if (!am) {
if (a.arity()==1) d=v.oneOf(a); else if (d==null) d=v.oneOf(Relation.UNIV); else d=v.oneOf(Relation.UNIV).and(d);
} else {
d = am(a, d, i, v);
}
ar=v.join(ar);
if (atuple==null) atuple=v; else atuple=atuple.product(v);
}
ans1=isIn(ar, ab.right);
switch(ab.op) {
case ISSEQ_ARROW_LONE:
case ANY_ARROW_LONE: case SOME_ARROW_LONE: case ONE_ARROW_LONE: case LONE_ARROW_LONE: ans1=ar.lone().and(ans1); break;
case ANY_ARROW_ONE: case SOME_ARROW_ONE: case ONE_ARROW_ONE: case LONE_ARROW_ONE: ans1=ar.one().and(ans1); break;
case ANY_ARROW_SOME: case SOME_ARROW_SOME: case ONE_ARROW_SOME: case LONE_ARROW_SOME: ans1=ar.some().and(ans1); break;
}
if (a.arity()>1) { Formula tmp=isIn(atuple, ab.left); if (tmp!=Formula.TRUE) ans1=tmp.implies(ans1); }
ans1=ans1.forAll(d);
// "R in A op-> B" means for each tuple b in B, there are "op" tuples in r that end with b.
Expression btuple=null, rb=r;
for(int i=b.arity(); i>0; i--) {
Variable v=Variable.unary("v" + Integer.toString(cnt++));
if (!am) {
if (b.arity()==1) d2=v.oneOf(b); else if (d2==null) d2=v.oneOf(Relation.UNIV); else d2=v.oneOf(Relation.UNIV).and(d2);
} else {
d2 = am(b, d2, i, v);
}
rb=rb.join(v);
if (btuple==null) btuple=v; else btuple=v.product(btuple);
}
ans2=isIn(rb, ab.left);
switch(ab.op) {
case LONE_ARROW_ANY: case LONE_ARROW_SOME: case LONE_ARROW_ONE: case LONE_ARROW_LONE: ans2=rb.lone().and(ans2); break;
case ONE_ARROW_ANY: case ONE_ARROW_SOME: case ONE_ARROW_ONE: case ONE_ARROW_LONE: ans2=rb.one().and(ans2); break;
case SOME_ARROW_ANY: case SOME_ARROW_SOME: case SOME_ARROW_ONE: case SOME_ARROW_LONE: ans2=rb.some().and(ans2); break;
}
if (b.arity()>1) { Formula tmp=isIn(btuple, ab.right); if (tmp!=Formula.TRUE) ans2=tmp.implies(ans2); }
ans2=ans2.forAll(d2);
// Now, put everything together
Formula ans=r.in(a.product(b)).and(ans1).and(ans2);
if (ab.op==ExprBinary.Op.ISSEQ_ARROW_LONE) {
Expression rr=r;
while(rr.arity()>1) rr=rr.join(Relation.UNIV);
ans=rr.difference(rr.join(A4Solution.KK_NEXT)).in(A4Solution.KK_ZERO).and(ans);
}
return ans;
}
private Decls am(final Expression a, Decls d, int i, Variable v) {
kodkod.ast.Decl ddd;
if (a.arity() == 1) {
assert i == 1;
ddd = v.oneOf(a);
} else {
ddd = v.oneOf(a.project(IntConstant.constant(i - 1)));
}
if (d == null)
d = ddd;
else
d = ddd.and(d);
return d;
}
/*===========================*/
/* Evaluates an ExprQt node. */
/*===========================*/
/** Adds a "one of" in front of X if X is unary and does not have a declared multiplicity. */
private static Expr addOne(Expr x) {
Expr save = x;
while(x instanceof ExprUnary) {
switch(((ExprUnary)x).op) {
case EXACTLYOF: case SETOF: case ONEOF: case LONEOF: case SOMEOF: return save;
case NOOP: x = ((ExprUnary)x).sub; continue;
default: break;
}
}
return (x.type().arity()!=1) ? x : ExprUnary.Op.ONEOF.make(x.span(), x);
}
/** Helper method that translates the quantification expression "op vars | sub" */
private Object visit_qt(final ExprQt.Op op, final ConstList<Decl> xvars, final Expr sub) throws Err {
if (op == ExprQt.Op.NO) {
return visit_qt(ExprQt.Op.ALL, xvars, sub.not());
}
if (op == ExprQt.Op.ONE || op == ExprQt.Op.LONE) {
boolean ok = true;
for(int i=0; i<xvars.size(); i++) {
Expr v = addOne(xvars.get(i).expr).deNOP();
if (v.type().arity()!=1 || v.mult()!=ExprUnary.Op.ONEOF) { ok=false; break; }
}
if (op==ExprQt.Op.ONE && ok) return ((Expression) visit_qt(ExprQt.Op.COMPREHENSION, xvars, sub)).one();
if (op==ExprQt.Op.LONE && ok) return ((Expression) visit_qt(ExprQt.Op.COMPREHENSION, xvars, sub)).lone();
}
if (op == ExprQt.Op.ONE) {
Formula f1 = (Formula) visit_qt(ExprQt.Op.LONE, xvars, sub);
Formula f2 = (Formula) visit_qt(ExprQt.Op.SOME, xvars, sub);
return f1.and(f2);
}
if (op == ExprQt.Op.LONE) {
QuantifiedFormula p1 = (QuantifiedFormula) visit_qt(ExprQt.Op.ALL, xvars, sub);
QuantifiedFormula p2 = (QuantifiedFormula) visit_qt(ExprQt.Op.ALL, xvars, sub);
Decls s1 = p1.decls(), s2 = p2.decls(), decls = null;
Formula f1 = p1.formula(), f2 = p2.formula();
Formula[] conjuncts = new Formula[s1.size()];
for(int i=0; i<conjuncts.length; i++) {
kodkod.ast.Decl d1 = s1.get(i), d2 = s2.get(i);
conjuncts[i] = d1.variable().eq(d2.variable());
if (decls==null) decls = d1.and(d2); else decls = decls.and(d1).and(d2);
}
return f1.and(f2).implies(Formula.and(conjuncts)).forAll(decls);
}
Decls dd = null;
List<Formula> guards = new ArrayList<Formula>();
for(Decl dep: xvars) {
final Expr dexexpr = addOne(dep.expr);
final Expression dv = cset(dexexpr);
for(ExprHasName dex: dep.names) {
final Variable v = Variable.nary(skolem(dex.label), dex.type().arity());
final kodkod.ast.Decl newd;
env.put((ExprVar)dex, v);
if (dex.type().arity()!=1) {
guards.add(isIn(v, dexexpr));
newd = v.setOf(dv);
} else switch(dexexpr.mult()) {
case SETOF: newd = v.setOf(dv); break;
case SOMEOF: newd = v.someOf(dv); break;
case LONEOF: newd = v.loneOf(dv); break;
default: newd = v.oneOf(dv);
}
if (frame!=null) frame.kv2typepos(v, dex.type(), dex.pos);
if (dd==null) dd = newd; else dd = dd.and(newd);
}
}
final Formula ans = (op==ExprQt.Op.SUM) ? null : cform(sub) ;
final IntExpression ians = (op!=ExprQt.Op.SUM) ? null : cint(sub) ;
for(Decl d: xvars) for(ExprHasName v: d.names) env.remove((ExprVar)v);
if (op==ExprQt.Op.COMPREHENSION) return ans.comprehension(dd); // guards.size()==0, since each var has to be unary
if (op==ExprQt.Op.SUM) return ians.sum(dd); // guards.size()==0, since each var has to be unary
if (op==ExprQt.Op.SOME) {
if (guards.size()==0) return ans.forSome(dd);
guards.add(ans);
return Formula.and(guards).forSome(dd);
} else {
if (guards.size()==0) return ans.forAll(dd);
return Formula.and(guards).implies(ans).forAll(dd);
}
}
/** {@inheritDoc} */
@Override public Object visit(ExprQt x) throws Err {
Expr xx = x.desugar();
if (xx instanceof ExprQt) x = (ExprQt)xx; else return visitThis(xx);
Object ans = visit_qt(x.op, x.decls, x.sub);
if (ans instanceof Formula) k2pos((Formula)ans, x);
return ans;
}
}