/* 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.ast;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import edu.mit.csail.sdg.alloy4.Pos;
import edu.mit.csail.sdg.alloy4.Err;
import edu.mit.csail.sdg.alloy4.ErrorType;
import edu.mit.csail.sdg.alloy4.ErrorSyntax;
import edu.mit.csail.sdg.alloy4.ErrorWarning;
import edu.mit.csail.sdg.alloy4.JoinableList;
import edu.mit.csail.sdg.alloy4.DirectedGraph;
import edu.mit.csail.sdg.alloy4.Util;
import edu.mit.csail.sdg.alloy4compiler.ast.Sig.PrimSig;
import edu.mit.csail.sdg.alloy4compiler.ast.Type.ProductType;
import static edu.mit.csail.sdg.alloy4compiler.ast.Sig.UNIV;
import static edu.mit.csail.sdg.alloy4compiler.ast.Sig.SIGINT;
import static edu.mit.csail.sdg.alloy4compiler.ast.Type.EMPTY;
/** Immutable; represents a unary expression of the form "(OP subexpression)"
*
* <p> <b>Invariant:</b> type!=EMPTY => sub.mult==0
*/
public final class ExprUnary extends Expr {
/** The unary operator. */
public final Op op;
/** The subexpression. */
public final Expr sub;
/** Caches the span() result. */
private Pos span=null;
//============================================================================================================//
/** {@inheritDoc} */
@Override public Pos span() {
Pos p=span;
if (p==null) { if (op==Op.NOOP && pos!=Pos.UNKNOWN) span=(p=pos); else span=(p=pos.merge(sub.span())); }
return p;
}
//============================================================================================================//
/** {@inheritDoc} */
@Override public void toString(StringBuilder out, int indent) {
if (indent<0) {
switch(op) {
case SOMEOF: out.append("some "); break;
case LONEOF: out.append("lone "); break;
case ONEOF: out.append("one "); break;
case SETOF: out.append("set "); break;
case EXACTLYOF: out.append("exactly "); break;
case CAST2INT: out.append("int["); sub.toString(out,-1); out.append(']'); return;
case CAST2SIGINT: out.append("Int["); sub.toString(out,-1); out.append(']'); return;
case NOOP: break;
default: out.append(op).append(' ');
}
sub.toString(out,-1);
} else {
for(int i=0; i<indent; i++) { out.append(' '); }
out.append(op).append(" with type=").append(type).append('\n');
sub.toString(out, indent+2);
}
}
//============================================================================================================//
/** Constructs an unary expression. */
private ExprUnary(Pos pos, Op op, Expr sub, Type type, long weight, JoinableList<Err> errors) {
super(pos, null, sub.ambiguous, type, (op==Op.EXACTLYOF||op==Op.SOMEOF||op==Op.LONEOF||op==Op.ONEOF||op==Op.SETOF)?1:0, weight, errors);
this.op = op;
this.sub = sub;
}
//============================================================================================================//
/** Returns true if we can determine the two expressions are equivalent; may sometimes return false. */
@Override public boolean isSame(Expr obj) {
if (op==Op.NOOP) return sub.isSame(obj);
while(obj instanceof ExprUnary && ((ExprUnary)obj).op==ExprUnary.Op.NOOP) obj=((ExprUnary)obj).sub;
if (obj==this) return true;
if (!(obj instanceof ExprUnary)) return false;
ExprUnary x=(ExprUnary)obj;
return op==x.op && sub.isSame(x.sub);
}
//============================================================================================================//
/** This class contains all possible unary operators. */
public enum Op {
/** :some x (where x is a unary set) */ SOMEOF("some of"),
/** :lone x (where x is a unary set) */ LONEOF("lone of"),
/** :one x (where x is a unary set) */ ONEOF("one of"),
/** :set x (where x is a set or relation) */ SETOF("set of"),
/** :exactly x (where x is a set or relation) */ EXACTLYOF("exactly of"),
/** not f (where f is a formula) */ NOT("!"),
/** no x (where x is a set or relation) */ NO("no"),
/** some x (where x is a set or relation) */ SOME("some"),
/** lone x (where x is a set or relation) */ LONE("lone"),
/** one x (where x is a set or relation) */ ONE("one"),
/** transpose */ TRANSPOSE("~"),
/** reflexive closure */ RCLOSURE("*"),
/** closure */ CLOSURE("^"),
/** cardinality of x (truncated to the current integer bitwidth) */ CARDINALITY("#"),
/** IntAtom-to-integer */ CAST2INT("Int->int"),
/** integer-to-IntAtom */ CAST2SIGINT("int->Int"),
/** No-Operation */ NOOP("NOOP");
/** The constructor */
private Op(String label) {this.label=label;}
/** The human readable label for this operator */
private final String label;
/** Construct an ExprUnary node.
* @param pos - the original position of the "unary operator" in the file (can be null if unknown)
* @param sub - the subexpression
*
* <p> Alloy4 disallows multiplicity like this: "variable : one (X lone-> Y)",
* <br> that is, a some/lone/one in front of an arrow multiplicity declaration.
* <br> Alloy4 does allow "variable : set (X lone-> Y)", where we ignore the word "set".
* <br> (This desugaring is done by the ExprUnary.Op.make() method, so ExprUnary's constructor never sees it)
*/
public final Expr make(Pos pos, Expr sub) { return make(pos, sub, null, 0); }
/** Construct an ExprUnary node.
* @param pos - the original position of the "unary operator" in the file (can be null if unknown)
* @param sub - the subexpression
* @param extraError - if nonnull, it will be appended as an extra error
* @param extraWeight - it's the amount of extra weight
*
* <p> Alloy4 disallows multiplicity like this: "variable : one (X lone-> Y)",
* <br> that is, a some/lone/one in front of an arrow multiplicity declaration.
* <br> Alloy4 does allow "variable : set (X lone-> Y)", where we ignore the word "set".
* <br> (This desugaring is done by the ExprUnary.Op.make() method, so ExprUnary's constructor never sees it)
*/
public final Expr make(Pos pos, Expr sub, Err extraError, long extraWeight) {
if (pos==null || pos==Pos.UNKNOWN) { if (this==NOOP) pos = sub.pos; else pos = sub.span(); }
JoinableList<Err> errors = sub.errors.make(extraError);
if (sub.mult!=0) {
if (this==SETOF) return sub;
if (this!=NOOP && extraError==null)
errors = errors.make(new ErrorSyntax(sub.span(), "Multiplicity expression not allowed here."));
// When you have a multiplicity expression like (A one->one B), and you call cint() on it,
// cint() will try to compose a NOOP node around the (A one->one B) with the error message "This must be an integer!"
// So in such a case, we will have a "NOOP" in front of a "MULTIPLICITY", and we don't want
// to clutter the output window with an extra useless report of "Multiplicity expression not allowed here!"
}
extraError=null;
switch(this) {
case NOOP: break;
case NOT: sub=sub.typecheck_as_formula(); break;
case CAST2SIGINT: sub=sub.typecheck_as_int(); break;
case CAST2INT: if (sub.type==Type.INT) return sub; else {sub=sub.typecheck_as_set(); break;} // Shortcut if already integer
default: sub=sub.typecheck_as_set();
}
Type type=sub.type;
if (sub.errors.isEmpty()) switch(this) {
case EXACTLYOF: case SOMEOF: case LONEOF: case ONEOF: case SETOF:
if (this==SETOF || this==EXACTLYOF) type=Type.removesBoolAndInt(sub.type); else type=sub.type.extract(1);
if (type==EMPTY) extraError=new ErrorType(sub.span(), "After the some/lone/one multiplicity symbol, " +
"this expression must be a unary set.\nInstead, its possible type(s) are:\n" + sub.type);
break;
case NOT: case NO: case SOME: case LONE: case ONE:
type=Type.FORMULA;
break;
case TRANSPOSE:
type=sub.type.transpose();
if (type==EMPTY) extraError=new ErrorType(sub.span(), "~ can be used only with a binary relation.\n" +
"Instead, its possible type(s) are:\n"+sub.type);
break;
case RCLOSURE: case CLOSURE:
type=sub.type.closure();
if (type==EMPTY) extraError=new ErrorType(sub.span(), label+" can be used only with a binary relation.\n" +
"Instead, its possible type(s) are:\n"+sub.type);
if (this==RCLOSURE) type=Type.make2(UNIV);
break;
case CARDINALITY:
type=Type.INT;
break;
case CAST2INT:
if (!sub.type.hasArity(1)) extraError=new ErrorType(sub.span(), "int[] can be used only with a unary set.\n" +
"Instead, its possible type(s) are:\n"+sub.type);
type=Type.INT;
break;
case CAST2SIGINT:
type=SIGINT.type;
break;
}
return new ExprUnary(pos, this, sub, type, extraWeight + sub.weight, errors.make(extraError));
}
/** Returns the human readable label for this operator */
@Override public final String toString() { return label; }
/** Returns the human readable label already encoded for HTML */
public final String toHTML() {
if (this == CAST2INT) return "Int->int";
if (this == CAST2SIGINT) return "int->Int";
return label;
}
}
//============================================================================================================//
/** {@inheritDoc} */
@Override public Expr resolve(Type p, Collection<ErrorWarning> warns) {
if (errors.size()>0) return this;
ErrorWarning w1=null, w2=null;
Type s=p;
switch(op) {
case NOT:
s=Type.FORMULA;
break;
case TRANSPOSE: case RCLOSURE: case CLOSURE:
if (warns!=null && op!=Op.TRANSPOSE && type.join(type).hasNoTuple())
w1=new ErrorWarning(pos, this+" is redundant since its domain and range are disjoint: "+sub.type.extract(2));
s = (op!=Op.TRANSPOSE) ? resolveClosure(p, sub.type) : sub.type.transpose().intersect(p).transpose() ;
if (warns!=null && s==EMPTY && p.hasTuple())
w2=new ErrorWarning(sub.span(),
"The value of this expression does not contribute to the value of the parent.\nParent's relevant type = "
+p+"\nThis expression's type = "+sub.type.extract(2));
break;
case CARDINALITY: case NO: case ONE: case SOME: case LONE:
s=Type.removesBoolAndInt(sub.type);
break;
case CAST2SIGINT:
s=Type.INT;
break;
case CAST2INT:
s=sub.type.intersect(SIGINT.type);
if (warns!=null && s.hasNoTuple())
w1=new ErrorWarning(sub.span(),
"This expression should contain Int atoms.\nInstead, its possible type(s) are:\n"+sub.type.extract(1));
break;
}
Expr sub = this.sub.resolve(s, warns);
if (w1!=null) warns.add(w1);
if (w2!=null) warns.add(w2);
return (sub==this.sub) ? this : op.make(pos, sub, null, weight-(this.sub.weight));
}
//============================================================================================================//
/** Helper method that computes the relevant type for a closure expression.
*
* <p> Return Value == { c1->c2 | c1->c2 in childType, AND exists p1->p2 in parentType
* where p1..c1..c2..p2 is a path in the closure graph }
*
* <p>
* We need to do this because of situations like this follow:
* Suppose e's type is "A->B + B->C".
* Therefore, ^e = A->B + B->C + A->C which makes sense.
* But as we compute the relevance type back down, we may have lost some entries,
* and possibly end up with only A->B + A->C so we need to rediscover the relevant edges.
*/
private static Type resolveClosure (Type parent, Type child) {
LinkedHashSet<PrimSig> nodes = new LinkedHashSet<PrimSig>();
DirectedGraph<PrimSig> graph = new DirectedGraph<PrimSig>();
// For each (v1->v2) in childType, add (v1->v2) into the graph.
for (ProductType c:child) if (c.arity()==2) {
PrimSig a=c.get(0), b=c.get(1);
nodes.add(a);
nodes.add(b);
graph.addEdge(a,b);
}
// For each distinct v1 and v2 in the graph where v1&v2!=empty, add the edges v1->v2 and v2->v1.
for (PrimSig a:nodes) for (PrimSig b:nodes) if (a!=b && a.intersects(b)) graph.addEdge(a,b);
// For each a->b in ParentType:
// 1) add a
// 2) add b
// 3) if a has subtypes/supertypes in the graph, connect between a and them.
// 4) if b has subtypes/supertypes in the graph, connect between b and them.
for (ProductType p:parent) if (p.arity()==2) {
PrimSig a=p.get(0), b=p.get(1);
// Add edges between a and all its subtypes and supertypes
if (!nodes.contains(a)) {
for (PrimSig x:nodes) if (a.intersects(x)) { graph.addEdge(a,x); graph.addEdge(x,a); }
nodes.add(a);
}
// Add edges between b and all its subtypes and supertypes
if (!nodes.contains(b)) {
for (PrimSig x:nodes) if (b.intersects(x)) { graph.addEdge(b,x); graph.addEdge(x,b); }
nodes.add(b);
}
}
// For each c1->c2 in childType, add c1->c2 into the finalType if there exists p1->p2 in parentType
// such that p1->..->c1->c2->..->p2 is a path in the graph.
Type answer=Type.EMPTY;
for (ProductType c:child) if (c.arity()==2) {
PrimSig c1=c.get(0), c2=c.get(1);
for (ProductType p:parent) if (p.arity()==2) {
PrimSig p1=p.get(0), p2=p.get(1);
if (graph.hasPath(p1,c1) && graph.hasPath(c2,p2)) { answer=answer.merge(c); break; }
}
}
return answer;
}
//============================================================================================================//
/** {@inheritDoc} */
public int getDepth() { return 1 + sub.getDepth(); }
/** {@inheritDoc} */
@Override final<T> T accept(VisitReturn<T> visitor) throws Err { return visitor.visit(this); }
/** {@inheritDoc} */
@Override public String getDescription() { return op==Op.NOOP ? sub.getDescription() : (op+" <i>" + type + "</i>"); }
/** {@inheritDoc} */
@Override public List<? extends Browsable> getSubnodes() { return op==Op.NOOP ? sub.getSubnodes() : Util.asList(sub); }
}