/*
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.common.css.compiler.passes;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.common.css.compiler.ast.CssBooleanExpressionNode;
import com.google.common.css.compiler.ast.CssBooleanExpressionNode.Type;
import java.util.Set;
/**
* An evaluator for boolean expressions. The evaluation returns a new boolean
* expression node corresponding to true, false, or to the condition if that was
* not recognized.
*
*/
public class BooleanExpressionEvaluator {
final CssBooleanExpressionNode expression;
final Set<String> trueConditions;
final Set<String> falseConditions;
final boolean theRestAreUnknown;
/**
* Evaluates a boolean expression given a set of true conditions. Conditions
* that are not in the set are assumed to be false.
*/
public BooleanExpressionEvaluator(CssBooleanExpressionNode expression,
Set<String> trueConditions) {
this(expression, trueConditions, ImmutableSet.<String>of(),
false /* the rest of the conditions are assumed known */);
Preconditions.checkArgument(!trueConditions.contains(null));
}
/**
* Evaluates a boolean expression given a set of true conditions and a set of
* false conditions. Conditions that are not in any of the sets are assumed to
* be unknown and are left in the resulting boolean expression.
*/
public BooleanExpressionEvaluator(CssBooleanExpressionNode expression,
Set<String> trueConditions, Set<String> falseConditions) {
this(expression, trueConditions, falseConditions,
true /* the rest of the conditions are assumed not known */);
Preconditions.checkArgument(!trueConditions.contains(null));
Preconditions.checkArgument(!falseConditions.contains(null));
}
private BooleanExpressionEvaluator(CssBooleanExpressionNode expression,
Set<String> trueConditions, Set<String> falseConditions,
boolean theRestAreUnknown) {
Preconditions.checkArgument(falseConditions.isEmpty() ||
!theRestAreUnknown);
Preconditions.checkArgument(
!trueConditions.contains(Type.TRUE_CONSTANT) &&
!trueConditions.contains(Type.FALSE_CONSTANT) &&
!falseConditions.contains(Type.TRUE_CONSTANT) &&
!falseConditions.contains(Type.FALSE_CONSTANT));
this.expression = expression;
this.trueConditions =
Sets.union(trueConditions, ImmutableSet.of(Type.TRUE_CONSTANT));
this.falseConditions =
Sets.union(falseConditions, ImmutableSet.of(Type.FALSE_CONSTANT));
this.theRestAreUnknown = theRestAreUnknown;
}
/**
* Evaluates the boolean expression associated with this evaluator.
*/
public CssBooleanExpressionNode evaluate() {
Object result = evaluateTree(expression);
if (result instanceof Boolean) {
return new CssBooleanExpressionNode(Type.CONSTANT,
(Boolean) result ? Type.TRUE_CONSTANT : Type.FALSE_CONSTANT);
}
return (CssBooleanExpressionNode) result;
}
/**
* Evaluates the tree corresponding to a boolean expression node.
*/
private Object evaluateTree(CssBooleanExpressionNode node) {
if (node.getType().isConstant()) {
String constantName = node.getValue();
if (trueConditions.contains(constantName)) {
return Boolean.TRUE;
}
if (falseConditions.contains(constantName)) {
return Boolean.FALSE;
}
if (theRestAreUnknown) {
// Copy of this constant node.
return new CssBooleanExpressionNode(node.getType(), node.getValue(),
node.getSourceCodeLocation());
} else {
return Boolean.FALSE;
}
}
// If we are here it means that the expression has operators.
if (node.getType().isUnaryOperator()) {
return evaluateTreeWithUnaryOperator(node);
} else {
// assert node.getType().isBinaryOperator();
return evaluateTreeWithBinaryOperator(node);
}
}
/**
* Evaluates the tree corresponding to a boolean expression node with an unary
* operator.
*/
private Object evaluateTreeWithUnaryOperator(CssBooleanExpressionNode node) {
// For a unary operator we only need to evaluate the left operand.
// assert node.getType() == Type.NOT;
Object operand = evaluateTree(node.getLeft());
if (operand instanceof Boolean) {
Boolean boolResult = (Boolean) operand;
return !boolResult;
}
// Return a tree for "!operand".
CssBooleanExpressionNode operandResult =
(CssBooleanExpressionNode) operand;
return new CssBooleanExpressionNode(node.getType(), node.getValue(),
operandResult, node.getSourceCodeLocation());
}
/**
* Evaluates the tree corresponding to a boolean expression node with a binary
* operator.
*/
private Object evaluateTreeWithBinaryOperator(
CssBooleanExpressionNode node) {
// For a binary operator we need to evaluate both left and right operands.
Object leftOperand = evaluateTree(node.getLeft());
if (leftOperand instanceof Boolean) {
Boolean leftBoolResult = (Boolean) leftOperand;
if (leftBoolResult == true && node.getType() == Type.OR) {
return Boolean.TRUE;
}
if (leftBoolResult == false && node.getType() == Type.AND) {
return Boolean.FALSE;
}
}
Object rightOperand = evaluateTree(node.getRight());
if (rightOperand instanceof Boolean) {
Boolean rightBoolResult = (Boolean) rightOperand;
if (leftOperand instanceof Boolean) {
if (node.getType() == Type.AND) {
return (Boolean) leftOperand && (Boolean) rightOperand;
} else {
// assert node.getType() == Type.OR;
return (Boolean) leftOperand || (Boolean) rightOperand;
}
} else {
if (rightBoolResult == true && node.getType() == Type.OR) {
return Boolean.TRUE;
}
if (rightBoolResult == false && node.getType() == Type.AND) {
return Boolean.FALSE;
}
// We either have (left && true) or (left || false).
return leftOperand;
}
} else {
if (leftOperand instanceof Boolean) {
// In this case the result is dictated by the right operand, as we can
// only have (TRUE && right) or (FALSE || right).
// assert leftOperand.equals(Boolean.TRUE) && node.getType() == Type.AND ||
// leftOperand.equals(Boolean.FALSE) && node.getType() == Type.OR;
return rightOperand;
} else {
// Return a tree for "leftOperand operator rightOperand".
CssBooleanExpressionNode leftResult =
(CssBooleanExpressionNode) leftOperand;
CssBooleanExpressionNode rightResult =
(CssBooleanExpressionNode) rightOperand;
return new CssBooleanExpressionNode(node.getType(), node.getValue(),
leftResult, rightResult, node.getSourceCodeLocation());
}
}
}
}