// Copyright 2014 The Bazel Authors. All rights reserved. // // 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.devtools.build.lib.syntax; import static com.google.devtools.build.lib.syntax.compiler.ByteCodeUtils.append; import com.google.common.base.Strings; import com.google.common.collect.Iterables; import com.google.devtools.build.lib.events.Location; import com.google.devtools.build.lib.syntax.ClassObject.SkylarkClassObject; import com.google.devtools.build.lib.syntax.SkylarkList.MutableList; import com.google.devtools.build.lib.syntax.SkylarkList.Tuple; import com.google.devtools.build.lib.syntax.compiler.ByteCodeMethodCalls; import com.google.devtools.build.lib.syntax.compiler.ByteCodeUtils; import com.google.devtools.build.lib.syntax.compiler.DebugInfo; import com.google.devtools.build.lib.syntax.compiler.DebugInfo.AstAccessors; import com.google.devtools.build.lib.syntax.compiler.Jump; import com.google.devtools.build.lib.syntax.compiler.Jump.PrimitiveComparison; import com.google.devtools.build.lib.syntax.compiler.LabelAdder; import com.google.devtools.build.lib.syntax.compiler.VariableScope; import net.bytebuddy.implementation.bytecode.ByteCodeAppender; import net.bytebuddy.implementation.bytecode.Duplication; import net.bytebuddy.implementation.bytecode.Removal; import net.bytebuddy.implementation.bytecode.StackManipulation; import java.util.ArrayList; import java.util.Collections; import java.util.EnumSet; import java.util.IllegalFormatException; import java.util.List; /** * Syntax node for a binary operator expression. */ public final class BinaryOperatorExpression extends Expression { private final Expression lhs; private final Expression rhs; private final Operator operator; public BinaryOperatorExpression(Operator operator, Expression lhs, Expression rhs) { this.lhs = lhs; this.rhs = rhs; this.operator = operator; } public Expression getLhs() { return lhs; } public Expression getRhs() { return rhs; } /** * Returns the operator kind for this binary operation. */ public Operator getOperator() { return operator; } @Override public String toString() { return lhs + " " + operator + " " + rhs; } /** * Implements comparison operators. * *
Publicly accessible for reflection and compiled Skylark code. */ public static int compare(Object lval, Object rval, Location location) throws EvalException { try { return EvalUtils.SKYLARK_COMPARATOR.compare(lval, rval); } catch (EvalUtils.ComparisonException e) { throw new EvalException(location, e); } } /** * Implements the "in" operator. * *
Publicly accessible for reflection and compiled Skylark code.
*/
public static boolean in(Object lval, Object rval, Location location) throws EvalException {
if (rval instanceof SkylarkList) {
for (Object obj : (SkylarkList) rval) {
if (obj.equals(lval)) {
return true;
}
}
return false;
} else if (rval instanceof SkylarkDict) {
return ((SkylarkDict, ?>) rval).containsKey(lval);
} else if (rval instanceof SkylarkNestedSet) {
return ((SkylarkNestedSet) rval).expandedSet().contains(lval);
} else if (rval instanceof String) {
if (lval instanceof String) {
return ((String) rval).contains((String) lval);
} else {
throw new EvalException(
location, "in operator only works on strings if the left operand is also a string");
}
} else {
throw new EvalException(
location, "in operator only works on lists, tuples, sets, dicts and strings");
}
}
@Override
Object doEval(Environment env) throws EvalException, InterruptedException {
Object lval = lhs.eval(env);
// Short-circuit operators
if (operator == Operator.AND) {
if (EvalUtils.toBoolean(lval)) {
return rhs.eval(env);
} else {
return lval;
}
}
if (operator == Operator.OR) {
if (EvalUtils.toBoolean(lval)) {
return lval;
} else {
return rhs.eval(env);
}
}
Object rval = rhs.eval(env);
switch (operator) {
case PLUS:
return plus(lval, rval, env, getLocation());
case PIPE:
return pipe(lval, rval, getLocation());
case MINUS:
return minus(lval, rval, getLocation());
case MULT:
return mult(lval, rval, getLocation());
case DIVIDE:
return divide(lval, rval, getLocation());
case PERCENT:
return percent(lval, rval, getLocation());
case EQUALS_EQUALS:
return lval.equals(rval);
case NOT_EQUALS:
return !lval.equals(rval);
case LESS:
return compare(lval, rval, getLocation()) < 0;
case LESS_EQUALS:
return compare(lval, rval, getLocation()) <= 0;
case GREATER:
return compare(lval, rval, getLocation()) > 0;
case GREATER_EQUALS:
return compare(lval, rval, getLocation()) >= 0;
case IN:
return in(lval, rval, getLocation());
case NOT_IN:
return !in(lval, rval, getLocation());
default:
throw new AssertionError("Unsupported binary operator: " + operator);
} // endswitch
}
@Override
public void accept(SyntaxTreeVisitor visitor) {
visitor.visit(this);
}
@Override
void validate(ValidationEnvironment env) throws EvalException {
lhs.validate(env);
rhs.validate(env);
}
@Override
ByteCodeAppender compile(VariableScope scope, DebugInfo debugInfo) throws EvalException {
AstAccessors debugAccessors = debugInfo.add(this);
List Publicly accessible for reflection and compiled Skylark code.
*/
public static Object plus(Object lval, Object rval, Environment env, Location location)
throws EvalException {
// int + int
if (lval instanceof Integer && rval instanceof Integer) {
return ((Integer) lval).intValue() + ((Integer) rval).intValue();
}
// string + string
if (lval instanceof String && rval instanceof String) {
return (String) lval + (String) rval;
}
if (lval instanceof SelectorValue || rval instanceof SelectorValue
|| lval instanceof SelectorList
|| rval instanceof SelectorList) {
return SelectorList.concat(location, lval, rval);
}
if ((lval instanceof Tuple) && (rval instanceof Tuple)) {
return Tuple.copyOf(Iterables.concat((Tuple) lval, (Tuple) rval));
}
if ((lval instanceof MutableList) && (rval instanceof MutableList)) {
return MutableList.concat((MutableList) lval, (MutableList) rval, env);
}
if (lval instanceof SkylarkDict && rval instanceof SkylarkDict) {
return SkylarkDict.plus((SkylarkDict, ?>) lval, (SkylarkDict, ?>) rval, env);
}
if (lval instanceof SkylarkClassObject && rval instanceof SkylarkClassObject) {
return SkylarkClassObject.concat(
(SkylarkClassObject) lval, (SkylarkClassObject) rval, location);
}
// TODO(bazel-team): Remove this case. Union of sets should use '|' instead of '+'.
if (lval instanceof SkylarkNestedSet) {
return new SkylarkNestedSet((SkylarkNestedSet) lval, rval, location);
}
throw typeException(lval, rval, Operator.PLUS, location);
}
/**
* Implements Operator.PIPE.
*
* Publicly accessible for reflection and compiled Skylark code.
*/
public static Object pipe(Object lval, Object rval, Location location) throws EvalException {
if (lval instanceof SkylarkNestedSet) {
return new SkylarkNestedSet((SkylarkNestedSet) lval, rval, location);
}
throw typeException(lval, rval, Operator.PIPE, location);
}
/**
* Implements Operator.MINUS.
*
* Publicly accessible for reflection and compiled Skylark code.
*/
public static Object minus(Object lval, Object rval, Location location) throws EvalException {
if (lval instanceof Integer && rval instanceof Integer) {
return ((Integer) lval).intValue() - ((Integer) rval).intValue();
}
throw typeException(lval, rval, Operator.MINUS, location);
}
/**
* Implements Operator.MULT.
*
* Publicly accessible for reflection and compiled Skylark code.
*/
public static Object mult(Object lval, Object rval, Location location) throws EvalException {
// int * int
if (lval instanceof Integer && rval instanceof Integer) {
return ((Integer) lval).intValue() * ((Integer) rval).intValue();
}
// string * int
if (lval instanceof String && rval instanceof Integer) {
return Strings.repeat((String) lval, ((Integer) rval).intValue());
}
// int * string
if (lval instanceof Integer && rval instanceof String) {
return Strings.repeat((String) rval, ((Integer) lval).intValue());
}
throw typeException(lval, rval, Operator.MULT, location);
}
/**
* Implements Operator.DIVIDE.
*
* Publicly accessible for reflection and compiled Skylark code.
*/
public static Object divide(Object lval, Object rval, Location location) throws EvalException {
// int / int
if (lval instanceof Integer && rval instanceof Integer) {
if (rval.equals(0)) {
throw new EvalException(location, "integer division by zero");
}
// Integer division doesn't give the same result in Java and in Python 2 with
// negative numbers.
// Java: -7/3 = -2
// Python: -7/3 = -3
// We want to follow Python semantics, so we use float division and round down.
return (int) Math.floor(new Double((Integer) lval) / (Integer) rval);
}
throw typeException(lval, rval, Operator.DIVIDE, location);
}
/**
* Implements Operator.PERCENT.
*
* Publicly accessible for reflection and compiled Skylark code.
*/
public static Object percent(Object lval, Object rval, Location location) throws EvalException {
// int % int
if (lval instanceof Integer && rval instanceof Integer) {
if (rval.equals(0)) {
throw new EvalException(location, "integer modulo by zero");
}
// Python and Java implement division differently, wrt negative numbers.
// In Python, sign of the result is the sign of the divisor.
int div = (Integer) rval;
int result = ((Integer) lval).intValue() % Math.abs(div);
if (result > 0 && div < 0) {
result += div; // make the result negative
} else if (result < 0 && div > 0) {
result += div; // make the result positive
}
return result;
}
// string % tuple, string % dict, string % anything-else
if (lval instanceof String) {
String pattern = (String) lval;
try {
if (rval instanceof Tuple) {
return Printer.formatToString(pattern, (Tuple) rval);
}
return Printer.formatToString(pattern, Collections.singletonList(rval));
} catch (IllegalFormatException e) {
throw new EvalException(location, e.getMessage());
}
}
throw typeException(lval, rval, Operator.PERCENT, location);
}
/**
* Returns a StackManipulation that calls the given operator's implementation method.
*
* The method must be named exactly as the lower case name of the operator and in addition to
* the operands require an Environment and Location.
*/
private static StackManipulation callImplementation(
VariableScope scope, AstAccessors debugAccessors, Operator operator) {
Class>[] parameterTypes =
new Class>[] {Object.class, Object.class, Environment.class, Location.class};
return new StackManipulation.Compound(
scope.loadEnvironment(),
debugAccessors.loadLocation,
ByteCodeUtils.invoke(
BinaryOperatorExpression.class, operator.name().toLowerCase(), parameterTypes));
}
/**
* Returns a StackManipulation that calls the given operator's implementation method.
*
* The method must be named exactly as the lower case name of the operator and in addition to
* the operands require a Location.
*/
private static StackManipulation callImplementation(
AstAccessors debugAccessors, Operator operator) {
Class>[] parameterTypes = new Class>[] {Object.class, Object.class, Location.class};
return new StackManipulation.Compound(
debugAccessors.loadLocation,
ByteCodeUtils.invoke(
BinaryOperatorExpression.class, operator.name().toLowerCase(), parameterTypes));
}
/**
* Throws an exception signifying incorrect types for the given operator.
*/
private static final EvalException typeException(
Object lval, Object rval, Operator operator, Location location) {
// NB: this message format is identical to that used by CPython 2.7.6 or 3.4.0,
// though python raises a TypeError.
// For more details, we'll hopefully have usable stack traces at some point.
return new EvalException(
location,
String.format(
"unsupported operand type(s) for %s: '%s' and '%s'",
operator,
EvalUtils.getDataTypeName(lval),
EvalUtils.getDataTypeName(rval)));
}
}