com.google.common.css.compiler.passes.BooleanExpressionEvaluator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of closure-stylesheets Show documentation
Show all versions of closure-stylesheets Show documentation
Closure Stylesheets is an extension to CSS that adds variables, functions,
conditionals, and mixins to standard CSS. The tool also supports
minification, linting, RTL flipping, and CSS class renaming.
/*
* 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 trueConditions;
final Set 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 trueConditions) {
this(expression, trueConditions, ImmutableSet.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 trueConditions, Set 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 trueConditions, Set 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 && node.getType() == Type.OR) {
return Boolean.TRUE;
}
if (!leftBoolResult && 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 {
return (Boolean) leftOperand || (Boolean) rightOperand;
}
} else {
if (rightBoolResult && node.getType() == Type.OR) {
return Boolean.TRUE;
}
if (!rightBoolResult && 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).
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());
}
}
}
}