/*
* Kodkod -- Copyright (c) 2005-2007, Emina Torlak
*
* 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 kodkod.util.nodes;
import static kodkod.ast.RelationPredicate.Name.ACYCLIC;
import static kodkod.ast.RelationPredicate.Name.FUNCTION;
import static kodkod.ast.RelationPredicate.Name.TOTAL_ORDERING;
import static kodkod.ast.operator.FormulaOperator.AND;
import static kodkod.ast.operator.FormulaOperator.IMPLIES;
import static kodkod.ast.operator.FormulaOperator.OR;
import java.util.Collections;
import java.util.EnumMap;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;
import kodkod.ast.BinaryFormula;
import kodkod.ast.ComparisonFormula;
import kodkod.ast.Comprehension;
import kodkod.ast.ConstantExpression;
import kodkod.ast.Decl;
import kodkod.ast.Decls;
import kodkod.ast.ExprToIntCast;
import kodkod.ast.Expression;
import kodkod.ast.Formula;
import kodkod.ast.IfExpression;
import kodkod.ast.IfIntExpression;
import kodkod.ast.IntComparisonFormula;
import kodkod.ast.IntToExprCast;
import kodkod.ast.MultiplicityFormula;
import kodkod.ast.NaryFormula;
import kodkod.ast.Node;
import kodkod.ast.NotFormula;
import kodkod.ast.QuantifiedFormula;
import kodkod.ast.Relation;
import kodkod.ast.RelationPredicate;
import kodkod.ast.SumExpression;
import kodkod.ast.Variable;
import kodkod.ast.operator.ExprCastOperator;
import kodkod.ast.operator.FormulaOperator;
import kodkod.ast.visitor.AbstractDetector;
import kodkod.ast.visitor.AbstractVoidVisitor;
import kodkod.util.collections.ArrayStack;
import kodkod.util.collections.IdentityHashSet;
import kodkod.util.collections.Stack;
/**
* A node annotated with information about
* structural sharing in its ast/dag. The class
* also provides utility methods for collecting
* various information about annotated nodes.
*
* @specfield node: N // annotated node
* @specfield source: node.*components ->one Node // maps the subnodes of this.node to nodes from
* // which they were derived by some transformation process
* // (e.g. skolemization, predicate inlining)
* @author Emina Torlak
*/
public final class AnnotatedNode<N extends Node> {
private final N node;
private final Set<Node> sharedNodes;
private final Map<? extends Node, ? extends Node> source;
/**
* Constructs a new annotator for the given node.
* @effects this.node' = node && this.source' = node.*components<:iden
*/
private AnnotatedNode(N node) {
this.node = node;
final SharingDetector detector = new SharingDetector();
node.accept(detector);
this.sharedNodes = Collections.unmodifiableSet(detector.sharedNodes());
this.source = Collections.emptyMap();
}
/**
* Constructs a new annotator for the given node and source map.
* @effects this.node' = node && this.source' = node.*components<:iden ++ source
*/
private AnnotatedNode(N node, Map<? extends Node, ? extends Node> source) {
this.node = node;
final SharingDetector detector = new SharingDetector();
node.accept(detector);
this.sharedNodes = Collections.unmodifiableSet(detector.sharedNodes());
this.source = source;
}
/**
* Returns an annotation for the given node. The source map of the returned annotation object
* maps each descendant of the node to itself.
* @return { a: AnnotatedNode<N> | a.node = node && a.source = node.*components<:iden }
*/
public static <N extends Node> AnnotatedNode<N> annotate(N node) { return new AnnotatedNode<N>(node); }
/**
* Returns an annotation for the given node. The source map of the returned annotation object
* maps each descendant of the node to its value in the given source map, or to itself
* if the given source map has no value for that descendant.
* @return { a: AnnotatedNode<N> | a.node = node && a.source = (node.*components<:iden) ++ source }
*/
public static <N extends Node> AnnotatedNode<N> annotate(N node, Map<? extends Node, ? extends Node> source) { return new AnnotatedNode<N>(node,source); }
/**
* Returns an annotation for an n-ary conjunctions of {@linkplain Nodes#roots(Formula) roots} of the given formula.
* The source map of the returned annotation object maps each descendant of the node to itself.
* The root conjunction itself is mapped to the input formula.
* @return { a: AnnotatedNode<Formula> | a.node = Formula.and(Nodes.roots(formula)) && a.source = (node.^components<:iden) + a.node->formula }
*/
public static AnnotatedNode<Formula> annotateRoots(Formula formula) {
final Formula flat = Formula.and(Nodes.roots(formula));
return new AnnotatedNode<Formula>(flat, Collections.singletonMap(flat, formula));
}
/**
* Returns this.node.
* @return this.node
*/
public final N node() {
return node;
}
/**
* Returns the source of the the given descendant of this.node.
* @requires n in this.node.*components
* @return this.source[n]
*/
public final Node sourceOf(Node n) {
final Node d = source.get(n);
return d==null ? n : d;
}
/**
* Returns the set of all non-leaf descendants of this.node that have more than one parent.
* @return {n: Node | some n.children && #(n.~components & this.node.*components) > 1 }
*/
public final Set<Node> sharedNodes() {
return sharedNodes;
}
/**
* Returns the set of all relations at the leaves of this annotated node.
* @return Relation & this.node.*components
*/
public final Set<Relation> relations() {
final Set<Relation> relations = new IdentityHashSet<Relation>();
final AbstractVoidVisitor visitor = new AbstractVoidVisitor() {
private final Set<Node> visited = new IdentityHashSet<Node>(sharedNodes.size());
protected boolean visited(Node n) {
return sharedNodes.contains(n) && !visited.add(n);
}
public void visit(Relation relation) {
relations.add(relation);
}
};
node.accept(visitor);
return relations;
}
/**
* Returns true if this.node contains a child whose meaning depends on
* integer bounds (i.e. an ExprToIntCast node with SUM operator or an IntToExprCast node or Expression.INTS constant).
* @return true if this.node contains a child whose meaning depends on
* integer bounds (i.e. an ExprToIntCast node with SUM operator or an IntToExprCast node or Expression.INTS constant).
*/
public final boolean usesInts() {
final AbstractDetector detector = new AbstractDetector(sharedNodes) {
public Boolean visit(IntToExprCast expr) {
return cache(expr, Boolean.TRUE);
}
public Boolean visit(ExprToIntCast intExpr) {
if (intExpr.op()==ExprCastOperator.CARDINALITY)
super.visit(intExpr);
return cache(intExpr, Boolean.TRUE);
}
public Boolean visit(ConstantExpression expr) {
return expr==Expression.INTS ? Boolean.TRUE : Boolean.FALSE;
}
};
return (Boolean)node.accept(detector);
}
/**
* Returns a map of RelationPredicate names to sets of top-level relation predicates with
* the corresponding names in this.node.
* @return a map of RelationPredicate names to sets of top-level relation predicates with
* the corresponding names in this.node. A predicate is considered 'top-level'
* if it is a component of the top-level conjunction, if any, of this.node.
*/
public final Map<RelationPredicate.Name, Set<RelationPredicate>> predicates() {
final PredicateCollector collector = new PredicateCollector(sharedNodes);
node.accept(collector);
return collector.preds;
}
/**
* Returns a Detector that will return TRUE when applied to a descendent
* of this.node iff the descendent contains a quantified formula.
* @return a Detector that will return TRUE when applied to a descendent
* of this.node iff the descendent contains a quantified formula.
*/
public final AbstractDetector quantifiedFormulaDetector() {
return new AbstractDetector(sharedNodes) {
public Boolean visit(QuantifiedFormula quantFormula) {
return cache(quantFormula, true);
}
};
}
/**
* Returns a Detector that will return TRUE when applied to a descendent
* of this.node iff the descendent contains a free variable.
* @return a Detector that will return TRUE when applied to a descendent
* of this.node iff the descendent contains a free variable.
*/
public final AbstractDetector freeVariableDetector() {
return new FreeVariableDetector(sharedNodes);
}
/**
* Returns a string representation of this annotated node.
* @return string representation of this annotated node.
*/
public String toString() {
final StringBuilder ret = new StringBuilder();
ret.append("node: ");
ret.append(node);
ret.append("\nshared nodes: ");
ret.append(sharedNodes);
ret.append("\nsources: ");
ret.append(source);
return ret.toString();
}
/**
* Detects shared non-leaf descendents of a given node.
*
* @specfield node: Node // node to which the analyzer is applied
*/
private static final class SharingDetector extends AbstractVoidVisitor {
/* maps each internal node with more than one parent to TRUE and all
* other internal nodes to FALSE */
final IdentityHashMap<Node,Boolean> sharingStatus;
/* @invariant numShareNodes = #sharingStatus.TRUE */
int numSharedNodes;
SharingDetector() {
sharingStatus = new IdentityHashMap<Node,Boolean>();
}
/**
* Returns the shared internal nodes of this.node. This method should
* be called only after this visitor has been applied to this.node.
* @return {n: Node | #(n.~children & node.*children) > 1 }
*/
IdentityHashSet<Node> sharedNodes() {
final IdentityHashSet<Node> shared = new IdentityHashSet<Node>(numSharedNodes);
for(Map.Entry<Node,Boolean> entry : sharingStatus.entrySet()) {
if (entry.getValue()==Boolean.TRUE)
shared.add(entry.getKey());
}
return shared;
}
/**
* Records the visit to the given node in the status map.
* If the node has not been visited before, it is mapped
* to Boolean.FALSE and false is returned. Otherwise,
* it is mapped to Boolean.TRUE and true is returned.
* The first time a Node is mapped to true, numSharedNodes
* is incremented by one.
* @effects no this.shared[node] => this.shared' = this.shared + node->FALSE,
* this.shared[node] = FALSE => this.shared' = this.shared + node->TRUE,
* this.shared' = this.shared
* @return this.shared'[node]
*/
protected final boolean visited(Node node) {
Boolean status = sharingStatus.get(node);
if (!Boolean.TRUE.equals(status)) {
if (status==null) {
status = Boolean.FALSE;
} else { // status == Boolean.FALSE
status = Boolean.TRUE;
numSharedNodes++;
}
sharingStatus.put(node,status);
}
return status;
}
}
/**
* A visitor that detects free variables of a node.
* @author Emina Torlak
*/
private static final class FreeVariableDetector extends AbstractDetector {
/* Holds the variables that are currently in scope, with the
* variable at the top of the stack being the last declared variable. */
private final Stack<Variable> varsInScope = new ArrayStack<Variable>();
/**
* Constructs a new free variable detector.
*/
FreeVariableDetector(Set<Node> sharedNodes) {
super(sharedNodes);
}
/**
* Visits the given comprehension, quantified formula, or sum expression.
* The method returns TRUE if the creator body contains any
* variable not bound by the decls; otherwise returns FALSE.
*/
@SuppressWarnings("unchecked")
private Boolean visit(Node creator, Decls decls, Node body) {
Boolean ret = lookup(creator);
if (ret!=null) return ret;
boolean retVal = false;
for(Decl decl : decls) {
retVal = decl.expression().accept(this) || retVal;
varsInScope.push(decl.variable());
}
retVal = ((Boolean)body.accept(this)) || retVal;
for(int i = decls.size(); i > 0; i--) {
varsInScope.pop();
}
return cache(creator, retVal);
}
/**
* Returns TRUE if the given variable is free in its parent, otherwise returns FALSE.
* @return TRUE if the given variable is free in its parent, otherwise returns FALSE.
*/
public Boolean visit(Variable variable) {
return Boolean.valueOf(varsInScope.search(variable)<0);
}
public Boolean visit(Decl decl) {
Boolean ret = lookup(decl);
if (ret!=null) return ret;
return cache(decl, decl.expression().accept(this));
}
public Boolean visit(Comprehension comprehension) {
return visit(comprehension, comprehension.decls(), comprehension.formula());
}
public Boolean visit(SumExpression intExpr) {
return visit(intExpr, intExpr.decls(), intExpr.intExpr());
}
public Boolean visit(QuantifiedFormula qformula) {
return visit(qformula, qformula.decls(), qformula.formula());
}
}
/**
* A visitor that detects and collects
* top-level relation predicates; i.e. predicates that
* are components in the top-level conjunction, if any, on ANY
* path starting at the root.
*/
private static final class PredicateCollector extends AbstractVoidVisitor {
protected boolean negated;
private final Set<Node> sharedNodes;
/* if a given node is not mapped at all, it means that it has not been visited;
* if it is mapped to FALSE, it has been visited with negated=FALSE,
* if it is mapped to TRUE, it has been visited with negated=TRUE,
* if it is mapped to null, it has been visited with both values of negated. */
private final Map<Node,Boolean> visited;
/* holds the top level predicates at the the end of the visit*/
final EnumMap<RelationPredicate.Name, Set<RelationPredicate>> preds;
/**
* Constructs a new collector.
* @effects this.negated' = false
*/
PredicateCollector(Set<Node> sharedNodes) {
this.sharedNodes = sharedNodes;
this.visited = new IdentityHashMap<Node,Boolean>();
this.negated = false;
preds = new EnumMap<RelationPredicate.Name, Set<RelationPredicate>>(RelationPredicate.Name.class);
preds.put(ACYCLIC, new IdentityHashSet<RelationPredicate>(4));
preds.put(TOTAL_ORDERING, new IdentityHashSet<RelationPredicate>(4));
preds.put(FUNCTION, new IdentityHashSet<RelationPredicate>(8));
}
/**
* Returns true if n has already been visited with the current value of the
* negated flag; otherwise returns false.
* @effects records that n is being visited with the current value of the negated flag
* @return true if n has already been visited with the current value of the
* negated flag; otherwise returns false.
*/
@Override
protected final boolean visited(Node n) {
if (sharedNodes.contains(n)) {
if (!visited.containsKey(n)) { // first visit
visited.put(n, Boolean.valueOf(negated));
return false;
} else {
final Boolean visit = visited.get(n);
if (visit==null || visit==negated) { // already visited with same negated value
return true;
} else { // already visited with different negated value
visited.put(n, null);
return false;
}
}
}
return false;
}
/**
* Calls visited(comp); comp's children are not top-level formulas
* so they are not visited.
*/
public void visit(Comprehension comp) {
visited(comp);
}
/**
* Calls visited(ifexpr); ifexpr's children are not top-level formulas
* so they are not visited.
*/
public void visit(IfExpression ifexpr) {
visited(ifexpr);
}
/**
* Calls visited(ifexpr); ifexpr's children are not top-level formulas
* so they are not visited.
*/
public void visit(IfIntExpression ifexpr) {
visited(ifexpr);
}
/**
* Calls visited(intComp); intComp's children are not top-level formulas
* so they are not visited.
*/
public void visit(IntComparisonFormula intComp) {
visited(intComp);
}
/**
* Calls visited(quantFormula); quantFormula's children are not top-level formulas
* so they are not visited.
*/
public void visit(QuantifiedFormula quantFormula) {
visited(quantFormula);
}
/**
* Visits the children of the given formula if it has not been visited already with
* the given value of the negated flag and if binFormula.op==IMPLIES && negated or
* binFormula.op==AND && !negated or binFormula.op==OR && negated. Otherwise does nothing.
* @see kodkod.ast.visitor.AbstractVoidVisitor#visit(kodkod.ast.BinaryFormula)
*/
public void visit(BinaryFormula binFormula) {
if (visited(binFormula)) return;
final FormulaOperator op = binFormula.op();
if ((!negated && op==AND) || (negated && op==OR)) { // op==AND || op==OR
binFormula.left().accept(this);
binFormula.right().accept(this);
} else if (negated && op==IMPLIES) { // !(a => b) = !(!a || b) = a && !b
negated = !negated;
binFormula.left().accept(this);
negated = !negated;
binFormula.right().accept(this);
}
}
/**
* Visits the children of the given formula if it has not been visited already with
* the given value of the negated flag and if formula.op==OR && negated or
* formula.op==AND && !negated. Otherwise does nothing.
* @see kodkod.ast.visitor.AbstractVoidVisitor#visit(kodkod.ast.NaryFormula)
*/
public void visit(NaryFormula formula) {
if (visited(formula)) return;
final FormulaOperator op = formula.op();
if ((!negated && op==AND) || (negated && op==OR)) { // op==AND || op==OR
for(Formula child : formula) {
child.accept(this);
}
}
}
/**
* Visits the children of the child of the child formula, with
* the negation of the current value of the negated flag,
* if it has not already been visited
* with the current value of this.negated; otherwise does nothing.
*/
public void visit(NotFormula not) {
if (visited(not)) return;
negated = !negated;
not.formula().accept(this);
negated = !negated;
}
/**
* Calls visited(compFormula); compFormula's children are not top-level formulas
* so they are not visited.
*/
public void visit(ComparisonFormula compFormula) {
visited(compFormula);
}
/**
* Calls visited(multFormula); multFormula's child is not top-level formulas
* so it is not visited.
*/
public void visit(MultiplicityFormula multFormula) {
visited(multFormula);
}
/**
* Records the visit to this predicate if it is not negated.
*/
public void visit(RelationPredicate pred) {
if (visited(pred)) return;
if (!negated) {
preds.get(pred.name()).add(pred);
}
}
}
}