Package edu.mit.csail.sdg.alloy4compiler.sim

Source Code of edu.mit.csail.sdg.alloy4compiler.sim.SimInstance

/* 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.sim;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import edu.mit.csail.sdg.alloy4.Env;
import edu.mit.csail.sdg.alloy4.Err;
import edu.mit.csail.sdg.alloy4.ErrorAPI;
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.Util;
import edu.mit.csail.sdg.alloy4.ConstList.TempList;
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.VisitReturn;
import edu.mit.csail.sdg.alloy4compiler.ast.Sig.Field;
import edu.mit.csail.sdg.alloy4compiler.ast.Sig.PrimSig;
import edu.mit.csail.sdg.alloy4compiler.ast.Sig.SubsetSig;
import edu.mit.csail.sdg.alloy4compiler.ast.Module;

/** Mutable; represents an instance. */

public final class SimInstance extends VisitReturn<Object> {

    /** The root module associated with this instance. */
    public final Module root;

    /** This maps the current local variables (LET, QUANT, Function Param) to the actual SimTupleset/Integer/Boolean */
    private Env<ExprVar,Object> env = new Env<ExprVar,Object>();

    /** This stores the "call backs" where you can supply Java code to efficiently handle certain functions/predicates. */
    private final Map<Func,SimCallback> callbacks;

    /** The exact values of each sig, field, and skolem (Note: it must not cache the value of any "defined" field, nor any builtin sig) */
    private final Map<Expr,SimTupleset> sfs = new LinkedHashMap<Expr,SimTupleset>();

    /** If nonnull, it caches the current value of STRING (this value must be cleared or updated when you change the value of sigs/fields/vars) */
    private SimTupleset cacheSTRING = null;

    /** If nonnull, it caches the current value of UNIV (this value must be cleared or updated when you change the value of sigs) */
    private SimTupleset cacheUNIV = null;

    /** Caches parameter-less functions to a Boolean, Integer, or SimTupleset. */
    private final Map<Func,Object> cacheForConstants = new IdentityHashMap<Func,Object>();

    /** This is used to detect "function recursion" (which we currently do not allow). */
    private final List<Func> current_function = new ArrayList<Func>();

    /** The chosen bitwidth */
    private final int bitwidth;

    /** The chosen maxseq length. */
    private final int maxseq;

    /** The shiftmask based on the chosen bitwidth. */
    private final int shiftmask;

    /** The minimum allowed integer based on the chosen bitwidth. */
    private final int min;

    /** The maximum allowed integer based on the chosen bitwidth. */
    private final int max;

    /** Helper method that encodes the given string using UTF-8 and write to the output stream. */
    private static void write(BufferedOutputStream out, String string) throws IOException {
        out.write(string.getBytes("UTF-8"));
    }

    /** Write the bitwidth, maxseq, set of all atoms, and map of all sig/field/var into the given file. */
    public void write(String filename) throws IOException {
        FileOutputStream fos = null;
        BufferedOutputStream bos = null;
        try {
            fos = new FileOutputStream(filename);
            bos = new BufferedOutputStream(fos);
            write(bos);
            bos.flush();
            bos.close();
            bos = null;
            fos.close();
            fos = null;
        } finally {
            Util.close(bos);
            Util.close(fos);
        }
    }

    /** Write the bitwidth, maxseq, set of all atoms, and map of all sig/field/var into the given file. */
    private void write(BufferedOutputStream out) throws IOException {
        write(out, "maxseq = " + maxseq + ("\n" + "bitwidth = ") + bitwidth + "\n");
        for(Map.Entry<Expr,SimTupleset> entry: sfs.entrySet()) {
            Expr e = entry.getKey();
            if (e instanceof Sig) write(out, "sig " + ((Sig)e).label + " = ");
            else if (e instanceof Field) write(out, "field " + ((Field)e).sig.label + " " + ((Field)e).label + " = ");
            else if (e instanceof ExprVar) write(out, "var " + ((ExprVar)e).label + " = ");
            else continue;
            entry.getValue().write(out);
            out.write('\n');
        }
    }

    /** Temporary buffer used by the parser; access to this buffer must be synchronized on SimInstance's class. */
    private static byte[] readcache = null;

    /** Helper method that read a non-negative integer followed by a line break. */
    private static int readNonNegativeIntThenLinebreak(BufferedInputStream bis) throws IOException {
        int n = 0;
        while(true) {
            int c = bis.read();
            if (c<0) throw new IOException("Unexpected EOF");
            if (c=='\n') return n;
            if (c>0 && c<=' ') continue; // skip white space
            if (!(c>='0' && c<='9')) throw new IOException("Expects a DIGIT or a white space!");
            n = n*10 + (c-'0');
        }
    }

    /** Helper method that read "key =" then return the key part (with leading and trailing spaces removed).
     * This method can only be called after you've synchronized on SimInstance's class.
     */
    private static String readkey(BufferedInputStream bis) throws IOException {
        int n = 0;
        while(true) {
            int c = bis.read();
            if (c<0) return "";
            if (c=='=') break;
            if (readcache==null) readcache = new byte[64]; // to ensure proper detection of out-of-memory error, this number must be 2^n for some n>=0
            while(n >= readcache.length) {
               byte[] readcache2 = new byte[readcache.length * 2];
               System.arraycopy(readcache, 0, readcache2, 0, readcache.length);
               readcache = readcache2;
            }
            readcache[n] = (byte)c;
            n++;
        }
        while(n>0 && readcache[n-1]>0 && readcache[n-1]<=' ') n--; // skip trailing spaces
        int i = 0;
        while(i<n && readcache[i]>0 && readcache[i]<=' ') i++; // skip leading space
        return new String(readcache, i, n-i, "UTF-8");
    }

    /** Construct a new simulation context by reading the given file. */
    public static synchronized SimInstance read(Module root, String filename, List<ExprVar> vars) throws Err, IOException {
        FileInputStream fis = null;
        BufferedInputStream bis = null;
        try {
            fis = new FileInputStream(filename);
            bis = new BufferedInputStream(fis);
            // read maxseq
            if (!readkey(bis).equals("maxseq")) throw new IOException("Expecting maxseq = ...");
            int maxseq = readNonNegativeIntThenLinebreak(bis);
            // read bitwidth
            if (!readkey(bis).equals("bitwidth")) throw new IOException("Expecting bitwidth = ...");
            int bitwidth = readNonNegativeIntThenLinebreak(bis);
            // construct the SimInstance object with no atoms and no relations
            SimInstance ans = new SimInstance(root, bitwidth, maxseq);
            // parse all the relations
            Map<String,SimTupleset> sfs = new HashMap<String,SimTupleset>();
            while(true) {
                String key = readkey(bis);
                if (key.length() == 0) break; // we don't expect any more data after this
                sfs.put(key, SimTupleset.read(bis));
            }
            // now for each user-supplied sig, if we saw its value earlier, then assign its value in the new SimInstance's sfs map
            for(final Sig s: root.getAllReachableSigs()) if (!s.builtin) {
                SimTupleset ts = sfs.get("sig " + s.label);
                if (ts!=null) ans.sfs.put(s, ts);
                for(final Field f: s.getFields()) if (!f.defined) {
                    ts = sfs.get("field " + s.label + " " + f.label);
                    if (ts!=null) ans.sfs.put(f, ts);
                }
            }
            // now for each user-supplied var, if we saw its value earlier, then assign its value in the new SimInstance's sfs map
            if (vars!=null) for(ExprVar v: vars) {
                SimTupleset ts = sfs.get("var " + v.label);
                if (ts!=null) ans.sfs.put(v, ts);
            }
            // close the files then return the answer
            bis.close();
            bis = null;
            fis.close();
            fis = null;
            return ans;
        } finally {
            // free the temporary array
            readcache = null;
            // if an exception occurred, we'll try to close to files anyway, since open file descriptors is a scarce resource
            Util.close(bis);
            Util.close(fis);
        }
    }

    /** Construct a new simulation context with the given bitwidth and the given maximum sequence length. */
    public SimInstance(Module root, int bitwidth, int maxseq) throws Err {
        if (bitwidth<1 || bitwidth>32) throw new ErrorType("Bitwidth must be between 1 and 32.");
        this.root = root;
        this.bitwidth = bitwidth;
        this.maxseq = maxseq;
        this.callbacks = new HashMap<Func,SimCallback>();
        if (bitwidth==32) { max=Integer.MAX_VALUE; min=Integer.MIN_VALUE; } else { max=(1<<(bitwidth-1))-1; min=(0-max)-1; }
        if (maxseq < 0)   throw new ErrorSyntax("The maximum sequence length cannot be negative.");
        if (maxseq > max) throw new ErrorSyntax("With integer bitwidth of "+bitwidth+", you cannot have sequence length longer than "+max);
        shiftmask = (1 << (32 - Integer.numberOfLeadingZeros(bitwidth-1))) - 1;
    }

    /** Construct a deep copy of this instance (except that it shares the same root Module object as the old instance) */
    public SimInstance(SimInstance old) throws Err {
        root = old.root;
        bitwidth = old.bitwidth;
        maxseq = old.maxseq;
        min = old.min;
        max = old.max;
        shiftmask = old.shiftmask;
        env = old.env.dup();
        cacheUNIV = old.cacheUNIV;
        cacheSTRING = old.cacheSTRING;
        callbacks = new HashMap<Func,SimCallback>(old.callbacks);
        for(Map.Entry<Expr,SimTupleset> e: old.sfs.entrySet()) sfs.put(e.getKey(), e.getValue());
    }

    /** Register a callback. */
    public void addCallback(Func predicateOrFunction, SimCallback callback) {
        callbacks.put(predicateOrFunction, callback);
    }

    /** Returns true if the given atom is an Int atom, or String atom, or is in at least one of the sig. */
    public boolean hasAtom(SimAtom atom) {
        if (atom.toString().length()>0) {
           char c = atom.toString().charAt(0);
           if (c=='-' || (c>='0' && c<='9') || c=='\"') return true;
        }
        for(Map.Entry<Expr,SimTupleset> e: sfs.entrySet()) {
           if (e.getKey() instanceof PrimSig && ((PrimSig)(e.getKey())).isTopLevel() && e.getValue().has(atom)) return true;
        }
        return false;
    }

    /** Create a fresh atom for the given sig, then return the newly created atom.
     * @throws ErrorAPI if attempting to add an atom to an abstract sig with children, or a builtin sig, or a subset sig.
     */
    public SimAtom makeAtom(Sig sig) throws Err {
        if (sig.builtin) throw new ErrorAPI("Cannot add an atom to a builtin sig.");
        if (!(sig instanceof PrimSig)) throw new ErrorAPI("Cannot add an atom to a subset sig.");
        PrimSig s = (PrimSig)sig;
        if (s.isAbstract!=null && !s.children().isEmpty()) throw new ErrorAPI("Cannot add an atom to an abstract parent sig.");
        String label = sig.label + "$";
        if (label.startsWith("this/")) label=label.substring(5);
        for(int i=0; ;i++) {
          SimAtom atom = SimAtom.make(label + i);
          if (hasAtom(atom)) continue;
          SimTupleset add = SimTupleset.make(SimTuple.make(atom));
          if (cacheUNIV!=null) cacheUNIV = cacheUNIV.union(add);
          for(; s!=null; s=s.parent) if (!s.builtin) {
              SimTupleset old = sfs.get(s);
              if (old==null || old.empty()) sfs.put(s, add); else if (!add.in(old)) sfs.put(s, old.union(add)); else break;
          }
          return atom;
        }
    }

    /** Delete an atom from all sigs/fields/skolem...
     * <p> The resulting instance may or may not satisfy all facts, and should be checked for consistency.
     * <p> Returns true if at least one sig/field/var changed its value as a result of this deletion.
     * @throws ErrorAPI if attempting to delete from "Int".
     */
    public boolean deleteAtom(SimAtom atom) throws Err {
       if (atom.toString().length()>0) {
          char c = atom.toString().charAt(0);
          if (c=='-' || (c>='0' && c<='9') || c=='\"') return false;
       }
       boolean changed = false;
       for(Map.Entry<Expr,SimTupleset> x: sfs.entrySet()) {
          SimTupleset oldvalue = x.getValue();
          SimTupleset newvalue = oldvalue.removeAll(atom);
          if (oldvalue.longsize() != newvalue.longsize()) { changed=true; x.setValue(newvalue); }
       }
       if (changed) { cacheUNIV=null; return true; } else { return false; }
    }

    /** Initializes the given sig to be associated with the given unary value; should only be called at the beginning.
     * <p> The resulting instance may or may not satisfy all facts, and should be checked for consistency.
     */
    public void init(Sig sig, SimTupleset value) throws Err {
        if (value==null) { sfs.remove(sig); return; }
        if (value.arity()>1) throw new ErrorType("Evaluator encountered an error: sig "+sig.label+" arity must not be " + value.arity());
        if (sig.builtin) throw new ErrorAPI("Evaluator cannot prebind the builtin sig \"" + sig.label + "\"");
        sfs.put(sig, value);
        cacheUNIV = null;
        cacheSTRING = null;
        cacheForConstants.clear();
    }

    /** Initializes the given field to be associated with the given unary value; should only be called at the beginning.
     * <p> The resulting instance may or may not satisfy all facts, and should be checked for consistency.
     */
    public void init(Field field, SimTupleset value) throws Err {
        if (value==null) { sfs.remove(field); return; }
        if (!value.empty() && value.arity()!=field.type().arity()) throw new ErrorType("Evaluator encountered an error: field "+field.label+" arity must not be " + value.arity());
        if (field.defined) throw new ErrorAPI("Evaluator cannot prebind the value of a defined field.");
        sfs.put(field, value);
        cacheUNIV = null;
        cacheSTRING = null;
        cacheForConstants.clear();
    }

    /** Initializes the given var to be associated with the given unary value; should only be called at the beginning.
     * <p> The resulting instance may or may not satisfy all facts, and should be checked for consistency.
     */
    public void init(ExprVar var, SimTupleset value) throws Err {
        if (value==null) { sfs.remove(var); return; }
        if (!value.empty() && value.arity()!=var.type().arity()) throw new ErrorType("Evaluator encountered an error: skolem "+var.label+" arity must not be " + value.arity());
        sfs.put(var, value);
        cacheUNIV = null;
        cacheSTRING = null;
        cacheForConstants.clear();
    }

    /** Truncate the given integer based on the current chosen bitwidth */
    private int trunc(int i) { return (i<<(32-bitwidth)) >> (32-bitwidth); }

    /** Convenience method that evalutes x and casts the result to be a boolean.
     * @return the boolean - if x evaluates to a boolean
     * @throws ErrorFatal - if x does not evaluate to a boolean
     */
    public boolean cform(Expr x) throws Err {
        if (!x.errors.isEmpty()) throw x.errors.pick();
        Object y = visitThis(x);
        if (y instanceof Boolean) return Boolean.TRUE.equals(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 int.
     * @return the int - if x evaluates to an int
     * @throws ErrorFatal - if x does not evaluate to an int
     */
    public int cint(Expr x) throws Err {
        if (!x.errors.isEmpty()) throw x.errors.pick();
        Object y = visitThis(x);
        if (y instanceof Integer) return trunc((Integer)y);
        throw new ErrorFatal(x.span(), "This should have been an integer expression.\nInstead it is "+y);
    }

    /** Convenience method that evalutes x and cast the result to be a tupleset
     * @return the tupleset - if x evaluates to a tupleset
     * @throws ErrorFatal - if x does not evaluate to a tupleset
     */
    public SimTupleset cset(Expr x) throws Err {
        if (!x.errors.isEmpty()) throw x.errors.pick();
        Object y = visitThis(x);
        if (y instanceof SimTupleset) return (SimTupleset)y;
        throw new ErrorFatal(x.span(), "This should have been a set or a relation.\nInstead it is "+y);
    }

    /** {@inheritDoc} */
    @Override public Object visit(ExprBinary x) throws Err {
        Expr a=x.left, b=x.right;
        switch(x.op) {
          case ARROW: case ANY_ARROW_LONE: case ANY_ARROW_ONE: case ANY_ARROW_SOME:
          case LONE_ARROW_ANY: case LONE_ARROW_LONE: case LONE_ARROW_ONE: case LONE_ARROW_SOME:
          case ONE_ARROW_ANY: case ONE_ARROW_LONE: case ONE_ARROW_ONE: case ONE_ARROW_SOME:
          case SOME_ARROW_ANY: case SOME_ARROW_LONE: case SOME_ARROW_ONE: case SOME_ARROW_SOME:
          case ISSEQ_ARROW_LONE:
              return cset(x.left).product(cset(x.right));
          case JOIN:
              if (x.left.isSame(Sig.UNIV)) {
                 SimTupleset tp = cset(x.right);
                 return tp.tail(tp.arity()-1);
              }
              if (x.right.isSame(Sig.UNIV)) {
                 SimTupleset tp = cset(x.left);
                 return tp.head(tp.arity()-1);
              }
              return cset(x.left).join(cset(x.right));
          case IMPLIES:    return !cform(x.left) || cform(x.right);
          case AND:        return cform(x.left) && cform(x.right);
          case OR:         return cform(x.left) || cform(x.right);
          case IFF:        return cform(x.left) == cform(x.right);
          case SHA:        return trunc(cint(x.left) >> (shiftmask & cint(x.right)));
          case SHR:        return trunc(cint(x.left) >>> (shiftmask & cint(x.right)));
          case SHL:        return trunc(cint(x.left) << (shiftmask & cint(x.right)));
          case INTERSECT:  return cset(x.left).intersect(cset(x.right));
          case GT:         return cint(x.left) > cint(x.right);
          case GTE:        return cint(x.left) >= cint(x.right);
          case LT:         return cint(x.left) < cint(x.right);
          case LTE:        return cint(x.left) <= cint(x.right);
          case NOT_GT:     return !(cint(x.left) > cint(x.right));
          case NOT_GTE:    return !(cint(x.left) >= cint(x.right));
          case NOT_LT:     return !(cint(x.left) < cint(x.right));
          case NOT_LTE:    return !(cint(x.left) <= cint(x.right));
          case DOMAIN:     return cset(x.left).domain(cset(x.right));
          case RANGE:      return cset(x.left).range(cset(x.right));
          case EQUALS:     return equal(x.left, x.right);
          case NOT_EQUALS: return !equal(x.left, x.right);
          case IN:         return isIn(x.left, x.right);
          case NOT_IN:     return !isIn(x.left, x.right);
          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 min;
              if (x.left.type().is_int) return trunc(cint(x.left)-cint(x.right)); else return cset(x.left).difference(cset(x.right));
          case PLUS:
              if (x.left.type().is_int) return trunc(cint(x.left)+cint(x.right)); else return cset(x.left).union(cset(x.right));
          case PLUSPLUS:
              return cset(x.left).override(cset(x.right));
          case MUL:
              return trunc(cint(x.left) * cint(x.right));
          case DIV:
              { int p=cint(x.left), q=cint(x.right), r=(p==0 ? 0 : (q==0 ? (p<0 ? 1 : -1) : (p/q))); return trunc(r); }
          case REM:
              { int p=cint(x.left), q=cint(x.right), r=(p==0 ? 0 : (q==0 ? (p<0 ? 1 : -1) : (p/q))); return trunc(p-r*q); }
        }
        throw new ErrorFatal(x.pos, "Unsupported operator ("+x.op+") encountered during ExprBinary.accept()");
    }

    /** {@inheritDoc} */
    @Override public Object visit(ExprList x) throws Err {
        if (x.op==ExprList.Op.AND) {
           for(Expr e:x.args) if (!cform(e)) return false;
           return true;
        }
        if (x.op==ExprList.Op.OR) {
           for(Expr e:x.args) if (cform(e)) return true;
           return false;
        }
        if (x.op==ExprList.Op.TOTALORDER) {
            SimTupleset elem = cset(x.args.get(0)), first = cset(x.args.get(1)), next = cset(x.args.get(2));
            return next.totalOrder(elem, first);
        }
        SimTupleset[] ans = new SimTupleset[x.args.size()];
        for(int i=1; i<ans.length; i++) {
           for(int j=0; j<i; j++) {
              if (ans[i]==null) if ((ans[i]=cset(x.args.get(i))).empty()) continue;
              if (ans[j]==null) if ((ans[j]=cset(x.args.get(j))).empty()) continue;
              if (ans[j].intersects(ans[i])) return false;
           }
        }
        return true;
    }

    /** {@inheritDoc} */
    @Override public Object visit(ExprCall x) throws Err {
        final Func f = x.fun;
        final int n = f.count();
        final Object candidate = n==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.");
        for(Func ff:current_function) if (ff==f) throw new ErrorSyntax(x.span(), ""+f+" cannot call itself recursively!");
        Env<ExprVar,Object> newenv = new Env<ExprVar,Object>();
        List<SimTupleset> list = new ArrayList<SimTupleset>(x.args.size());
        for(int i=0; i<n; i++) { SimTupleset ts = cset(x.args.get(i)); newenv.put(f.get(i), ts);  list.add(ts); }
        final SimCallback cb = callbacks.get(f);
        if (cb!=null) {
           try {
              Object answer = cb.compute(f, list);
              if (answer!=null) {
                 if (x.args.size()==0) cacheForConstants.put(f, answer);
                 return answer;
              }
           } catch(Exception ex) {
              // if the callback failed, we can just continue with our original attempt to evaluate this call
           }
        }
        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 (f.count()==0) cacheForConstants.put(f, ans);
        return ans;
    }

    /** {@inheritDoc} */
    @Override public Object visit(ExprConstant x) throws Err {
        switch(x.op) {
          case NUMBER:
             int n = x.num();
             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 n;
          case FALSE: return Boolean.FALSE;
          case TRUE: return Boolean.TRUE;
          case MIN: return min;
          case MAX: return max;
          case EMPTYNESS: return SimTupleset.EMPTY;
          case STRING: return SimTupleset.make(x.string);
          case NEXT: return SimTupleset.makenext(min, max);
          case IDEN: return cset(Sig.UNIV).iden();
        }
        throw new ErrorFatal(x.pos, "Unsupported operator ("+x.op+") encountered during ExprConstant.accept()");
    }

    /** {@inheritDoc} */
    @Override public Object visit(ExprITE x) throws Err {
        if (cform(x.cond)) return visitThis(x.left); else return visitThis(x.right);
    }

    /** {@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;
    }

    /** {@inheritDoc} */
    @Override public Object visit(ExprUnary x) throws Err {
        switch(x.op) {
          case EXACTLYOF:
          case LONEOF:
          case ONEOF:
          case SETOF:
          case SOMEOF:      return cset(x.sub);
          case NOOP:        return visitThis(x.sub);
          case CARDINALITY: return trunc(cset(x.sub).size());
          case NO:          return cset(x.sub).empty();
          case LONE:        return cset(x.sub).longsize()<=1;
          case ONE:         return cset(x.sub).longsize()==1;
          case SOME:        return cset(x.sub).longsize()>=1;
          case NOT:         return cform(x.sub) ? Boolean.FALSE : Boolean.TRUE;
          case CAST2SIGINT: return SimTupleset.make(SimTuple.make(SimAtom.make(cint(x.sub))));
          case CAST2INT:    return trunc(cset(x.sub).sum());
          case CLOSURE:     return cset(x.sub).closure();
          case RCLOSURE:    return cset(x.sub).closure().union(cset(ExprConstant.IDEN));
          case TRANSPOSE:   return cset(x.sub).transpose();
        }
        throw new ErrorFatal(x.pos, "Unsupported operator ("+x.op+") encountered during ExprUnary.accept()");
    }

    /** {@inheritDoc} */
    @Override public Object visit(ExprVar x) throws Err {
        Object ans = env.get(x);
        if (ans==null) ans = sfs.get(x);
        if (ans==null) {
            SimAtom a = SimAtom.make(x.label);
            if (!hasAtom(a)) throw new ErrorFatal(x.pos, "Variable \""+x+"\" is not bound to a legal value during translation.\n");
            ans = SimTupleset.make(SimTuple.make(a));
        }
        return ans;
    }

    /** {@inheritDoc} */
    @Override public SimTupleset visit(Sig x) throws Err {
       if (x.isSame(Sig.NONE)) return SimTupleset.EMPTY;
       if (x.isSame(Sig.SEQIDX)) return SimTupleset.make(0, maxseq-1);
       if (x.isSame(Sig.SIGINT)) return SimTupleset.make(min, max);
       if (x.isSame(Sig.STRING)) {
          if (cacheSTRING == null) {
             cacheSTRING = SimTupleset.EMPTY;
             for(Map.Entry<Expr,SimTupleset> e: sfs.entrySet()) if (e.getKey() instanceof Field || e.getKey() instanceof ExprVar) {
               for(SimTuple t: e.getValue()) for(int i=t.arity()-1; i>=0; i--) {
                 String a = t.get(i).toString();
                 if (a.length()>0 && a.charAt(0)=='"') cacheSTRING = cacheSTRING.union(SimTuple.make(t.get(i)));
               }
             }
          }
          return cacheSTRING;
       }
       if (x==Sig.UNIV) {
          if (cacheUNIV == null) {
             cacheUNIV = SimTupleset.make(min, max);
             for(Map.Entry<Expr,SimTupleset> e: sfs.entrySet())
                if (e.getKey() instanceof PrimSig && ((PrimSig)(e.getKey())).isTopLevel())
                   cacheUNIV = cacheUNIV.union(e.getValue());
             cacheUNIV = cacheUNIV.union(visit(Sig.STRING));
          }
          return cacheUNIV;
       }
       Object ans = sfs.get(x);
       if (ans instanceof SimTupleset) return (SimTupleset)ans; else throw new ErrorFatal("Unknown sig "+x+" encountered during evaluation.");
    }

    /** {@inheritDoc} */
    @Override public SimTupleset visit(Field x) throws Err {
        if (x.defined) {
            final ExprVar v = (ExprVar)(x.sig.decl.get());
            final Expr b = x.decl().expr;
            final Env<ExprVar,Object> oldenv = env;
            env = new Env<ExprVar,Object>();
            if (!b.hasVar(v)) { SimTupleset ans=cset(x.sig).product(cset(b)); env=oldenv; return ans; }
            SimTupleset ans = SimTupleset.EMPTY;
            for(SimTuple a: visit(x.sig)) {
                SimTupleset left = SimTupleset.make(a);
                env.put(v, left);
                SimTupleset right = cset(b);
                env.remove(v);
                ans = left.product(right).union(ans);
            }
            env = oldenv;
            return ans;
        }
        Object ans = sfs.get(x);
        if (ans instanceof SimTupleset) return (SimTupleset)ans; else throw new ErrorFatal("Unknown field "+x+" encountered during evaluation.");
    }

    /** Helper method for enumerating all possibilties for a quantification-expression. */
    private int enumerate(final TempList<SimTuple> store, int sum, final ExprQt x, final Expr body, final int i) throws Err { // if op is ALL NO SOME ONE LONE then it always returns 0 1 2
       final int n = x.count();
       final ExprVar v = x.get(i);
       final Expr bound = x.getBound(i);
       final SimTupleset e = cset(bound);
       final Iterator<SimTupleset> it;
       switch(bound.mult()) {
         case LONEOF: it = e.loneOf(); break;
         case ONEOF:  it = e.oneOf()break;
         case SOMEOF: it = e.someOf(); break;
         default:     it = e.setOf();
       }
       while(it.hasNext()) {
          final SimTupleset binding = it.next();
          if (bound.mult==2 && !isIn(binding, bound)) continue;
          env.put(v, binding);
          if (i<n-1) sum = enumerate(store, sum, x, body, i+1);
             else if (x.op==ExprQt.Op.SUM) sum += cint(body);
             else if (x.op!=ExprQt.Op.COMPREHENSION) sum += cform(body)?1:0;
             else if (cform(body)) {
               SimTuple a=null, b;
               for(int j=0; j<n; j++) { b=((SimTupleset)(env.get(x.get(j)))).getTuple(); if (a==null) a=b; else a=a.product(b); }
               store.add(a);
             }
          env.remove(v);
          if (sum>=2 && x.op!=ExprQt.Op.COMPREHENSION && x.op!=ExprQt.Op.SUM) return 2; // no need to enumerate further
       }
       return sum;
    }

    /** {@inheritDoc} */
    @Override public Object visit(ExprQt x) throws Err {
        Expr xx = x.desugar();
        if (xx instanceof ExprQt) x = (ExprQt)xx; else return visitThis(xx);
        if (x.op == ExprQt.Op.COMPREHENSION) {
           TempList<SimTuple> ans = new TempList<SimTuple>();
           enumerate(ans, 0, x, x.sub, 0);
           return SimTupleset.make(ans.makeConst());
        }
        if (x.op == ExprQt.Op.ALLreturn enumerate(null, 0, x, x.sub.not(), 0) == 0;
        if (x.op == ExprQt.Op.NO)   return enumerate(null, 0, x, x.sub,       0) == 0;
        if (x.op == ExprQt.Op.SOME) return enumerate(null, 0, x, x.sub,       0) >= 1;
        if (x.op == ExprQt.Op.LONE) return enumerate(null, 0, x, x.sub,       0) <= 1;
        if (x.op == ExprQt.Op.ONEreturn enumerate(null, 0, x, x.sub,       0) == 1;
        if (x.op == ExprQt.Op.SUMreturn trunc(enumerate(null, 0, x, x.sub, 0));
        throw new ErrorFatal(x.pos, "Unsupported operator ("+x.op+") encountered during ExprQt.accept()");
    }

    /** Helper method that evaluates the formula "a in b" where b.mult==0 */
    public boolean isIn(SimTuple a, Expr b) throws Err {
        b = b.deNOP();
        if (b instanceof PrimSig && ((PrimSig)b).builtin) {
           if (a.arity()!=1) return false;
           if (b.isSame(Sig.UNIV)) return true;
           if (b.isSame(Sig.NONE)) return false;
           if (b.isSame(Sig.SEQIDX)) { Integer i = a.get(0).toInt(null); return i!=null && i>=0 && i<maxseq; }
           if (b.isSame(Sig.SIGINT)) { Integer i = a.get(0).toInt(null); return i!=null; }
           if (b.isSame(Sig.STRING)) { String at = a.get(0).toString(); return at.length()>0 && (at.charAt(0)=='\"'); }
        }
        if (b instanceof ExprBinary && ((ExprBinary)b).op==ExprBinary.Op.ARROW) {
           Expr left = ((ExprBinary)b).left, right = ((ExprBinary)b).right;
           int ll = left.type().arity(), rr = right.type().arity();
           if (ll <= rr) return isIn(a.head(ll), left) && isIn(a.tail(rr), right);
           return isIn(a.tail(rr), right) && isIn(a.head(ll), left);
        }
        if (b instanceof ExprBinary && ((ExprBinary)b).op==ExprBinary.Op.PLUS) {
           return isIn(a, ((ExprBinary)b).left) || isIn(a, ((ExprBinary)b).right);
        }
        if (b instanceof ExprBinary && ((ExprBinary)b).op==ExprBinary.Op.MINUS) {
           return isIn(a, ((ExprBinary)b).left) && !isIn(a, ((ExprBinary)b).right);
        }
        return cset(b).has(a);
    }

    /** Helper method that evaluates the formula "a = b" */
    public boolean equal(Expr a, Expr b) throws Err {
        if (a.type().is_bool) return cform(a)==cform(b);
        if (a.type().is_int) return cint(a)==cint(b);
        if (a.type().arity()<=0 || a.type().arity()!=b.type().arity()) return false; // type mismatch
        if (a.isSame(b)) return true; else return cset(a).equals(cset(b));
    }

    /** Helper method that evaluates the formula "a in b" */
    public boolean isIn(Expr a, Expr b) throws Err {
        if (a.type().arity()<=0 || a.type().arity()!=b.type().arity()) return false; // type mismatch
        if (b.isSame(Sig.UNIV)) return true; // everything is a subset of UNIV
        if (a.isSame(b)) return true; // if a==b then a is a subset of b
        return isIn(cset(a), b);
    }

    /** Helper method that evaluates the formula "a in b" */
    private boolean isIn(SimTupleset a, Expr b) throws Err {
        b = b.deNOP();
        if (b instanceof ExprBinary && b.mult!=0 && ((ExprBinary)b).op.isArrow) {
            // Handles possible "binary" or higher-arity multiplicity
            return isInBinary(a, (ExprBinary)b);
        }
        if (b instanceof ExprUnary) {
            // Handles possible "unary" multiplicity
            ExprUnary y = (ExprUnary)b;
            if      (y.op==ExprUnary.Op.EXACTLYOF) { b = y.sub.deNOP(); return a.equals(cset(b)); }
            else if (y.op==ExprUnary.Op.ONEOF)     { b = y.sub.deNOP(); if (!(a.longsize()==1)) return false; }
            else if (y.op==ExprUnary.Op.LONEOF)    { b = y.sub.deNOP(); if (!(a.longsize()<=1)) return false; }
            else if (y.op==ExprUnary.Op.SOMEOF)    { b = y.sub.deNOP(); if (!(a.longsize()>=1)) return false; }
            else if (y.op!=ExprUnary.Op.SETOF)     { b = y.sub.deNOP(); }
        }
        for(SimTuple t:a) if (!isIn(t, b)) return false;
        return true;
    }

    /** Helper method that evaluates the formula "r in (a ?->? b)" */
    private boolean isInBinary(SimTupleset R, ExprBinary ab) throws Err {
       // Special check for ISSEQ_ARROW_LONE
       if (ab.op == ExprBinary.Op.ISSEQ_ARROW_LONE) {
          List<SimAtom> list = R.getAllAtoms(0);
          int next = 0;
          SimAtom nextStr = SimAtom.make("0");
          while (list.size() > 0) {
             for (int n=list.size(), i=0; ; i++) if (i>=n) return false; else if (nextStr==list.get(i)) { list.set(i, list.get(n-1)); list.remove(n-1); next++; break; }
             if (list.size()==0) break;
             if (next<0 || next>=maxseq) return false; // list.size()>0 and yet we've exhausted 0..maxseq-1, so indeed there are illegal atoms
             nextStr = SimAtom.make(next);
          }
       }
       // "R in A ->op B" means for each tuple a in A, there are "op" tuples in r that begins with a.
       for(SimTuple left: cset(ab.left)) {
         SimTupleset ans = R.beginWith(left);
         switch(ab.op) {
            case ISSEQ_ARROW_LONE:
            case ANY_ARROW_LONE: case SOME_ARROW_LONE: case ONE_ARROW_LONE: case LONE_ARROW_LONE: if (!(ans.longsize()<=1)) return false; else break;
            case ANY_ARROW_ONE:  case SOME_ARROW_ONE:  case ONE_ARROW_ONE:  case LONE_ARROW_ONE:  if (!(ans.longsize()==1)) return false; else break;
            case ANY_ARROW_SOME: case SOME_ARROW_SOME: case ONE_ARROW_SOME: case LONE_ARROW_SOME: if (!(ans.longsize()>=1)) return false; else break;
         }
         if (!isIn(ans, ab.right)) return false;
       }
       // "R in A op-> B" means for each tuple b in B, there are "op" tuples in r that end with b.
       for(SimTuple right: cset(ab.right)) {
         SimTupleset ans = R.endWith(right);
         switch(ab.op) {
            case LONE_ARROW_ANY: case LONE_ARROW_SOME: case LONE_ARROW_ONE: case LONE_ARROW_LONE: if (!(ans.longsize()<=1)) return false; else break;
            case ONE_ARROW_ANY:  case ONE_ARROW_SOME:  case ONE_ARROW_ONE:  case ONE_ARROW_LONE:  if (!(ans.longsize()==1)) return false; else break;
            case SOME_ARROW_ANY: case SOME_ARROW_SOME: case SOME_ARROW_ONE: case SOME_ARROW_LONE: if (!(ans.longsize()>=1)) return false; else break;
         }
         if (!isIn(ans, ab.left)) return false;
       }
       return true;
    }

    /** Checks whether this instance satisfies every fact defined in the given model.
     * @param world - this must be the root of the Alloy model
     * @return an empty String if yes, nonempty String if no
     */
    public String validate(Module world) {
       try {
          for(Sig s: world.getAllReachableSigs()) if (!s.builtin) {
             if (s.isLone!=null && !(visit(s).longsize()<=1)) return "There can be at most one "+s;
             if (s.isOne !=null && !(visit(s).longsize()==1)) return "There must be exactly one "+s;
             if (s.isSome!=null && !(visit(s).longsize()>=1)) return "There must be at least one "+s;
             if (s instanceof SubsetSig) {
                SubsetSig p = (SubsetSig)s;
                Expr sum = null;
                for(Sig par: p.parents) sum = par.plus(sum);
                if (p.exact) {
                   if (!equal(s, sum)) return "Sig "+s+" must be equal to the union of its parents "+p.parents;
                } else {
                   if (!isIn(s, sum)) return "Sig "+s+" must be equal or subset of its parents "+p.parents;
                }
             } else if (s != Sig.UNIV && s != Sig.NONE) {
                PrimSig p = (PrimSig)s;
                if (!isIn(s, p.parent)) return "Sig "+s+" must be equal or subset of its parent "+p.parent;
             }
             if (s.isAbstract!=null) {
                Expr sum = null;
                for(Sig x: ((PrimSig)s).children()) sum = x.plus(sum);
                if (sum!=null && !equal(s, sum)) return "Abstract sig "+s+" must be equal to the union of its subsigs";
             }
             for(Decl d: s.getFieldDecls()) for(ExprHasName f: d.names) if (!((Field)f).defined) {
                if (!cform(s.decl.get().join(f).in(d.expr).forAll(s.decl))) {
                   return "Field "+f+" violated its bound: " + visit((Field)f) + "\n" + d.expr;
                }
                SimTupleset setS = visit(s);
                SimTupleset setF = visit((Field)f);
                for(SimAtom x:setF.getAllAtoms(0)) if (!setS.has(x)) return "Field "+f+" first column has extra atom: "+setF+" not in "+setS;
             }
             for(Decl d: s.getFieldDecls()) {
                if (d.disjoint!=null && d.names.size()>0) {
                   if (!cform(ExprList.makeDISJOINT(null, null, d.names))) return "Fields must be disjoint.";
                }
                if (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());
                   if (!cform(formula.forAll(that).forAll(s.decl))) return "Fields must be disjoint.";
                }
             }
             for(Expr f: s.getFacts()) {
                if (!cform(f.forAll(s.decl))) {
                   f = f.deNOP(); if (f instanceof ExprUnary) {
                      ExprUnary u = (ExprUnary)f;
                      f = u.sub.deNOP();
                      if (f instanceof ExprBinary) {
                         ExprBinary b = (ExprBinary)f;
                         if (b.op == ExprBinary.Op.JOIN && b.left.isSame(s.decl.get()) && b.right.deNOP() instanceof Field) {
                            String n = ((Field)(b.right.deNOP())).label;
                            if (u.op == ExprUnary.Op.SOME) return "The "+n+" cannot be empty.";
                            if (u.op == ExprUnary.Op.LONE) return "The "+n+" cannot have more than one value.";
                            if (u.op == ExprUnary.Op.ONEreturn "The "+n+" must have exactly one value.";
                            if (u.op == ExprUnary.Op.NO)   return "The "+n+" must be empty.";
                         }
                      }
                   }
                   return "Cannot violate a consistency constraint";
                }
             }
          }
          for(Module m: world.getAllReachableModules()) for(Pair<String,Expr> f: m.getAllFacts()) if (!cform(f.b)) {
             String err = f.a;
             if (err.matches("^fact\\$[0-9][0-9]*")) err = f.b.toString();
             if (err.length()>=2 && err.startsWith("\"") && err.endsWith("\"")) err = err.substring(1, err.length()-1);
             return "Violation: " + err;
          }
          return "";
       } catch(Err ex) {
          return "An internal error has occured:\n" + ex.dump();
       }
    }
}
TOP

Related Classes of edu.mit.csail.sdg.alloy4compiler.sim.SimInstance

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.