blob: 1c9c8ad024b0043147522c036fb520fd09dbdb87 [file] [log] [blame]
// 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 net.starlark.java.eval;
import java.util.IllegalFormatException;
import java.util.Map;
import javax.annotation.Nullable;
import net.starlark.java.syntax.TokenKind;
/** Internal declarations used by the evaluator. */
final class EvalUtils {
private EvalUtils() {}
static void addIterator(Object x) {
if (x instanceof Mutability.Freezable) {
((Mutability.Freezable) x).updateIteratorCount(+1);
}
}
static void removeIterator(Object x) {
if (x instanceof Mutability.Freezable) {
((Mutability.Freezable) x).updateIteratorCount(-1);
}
}
// The following functions for indexing and slicing match the behavior of Python.
/**
* Resolves a positive or negative index to an index in the range [0, length), or throws
* EvalException if it is out of range. If the index is negative, it counts backward from length.
*/
static int getSequenceIndex(int index, int length) throws EvalException {
int actualIndex = index;
if (actualIndex < 0) {
actualIndex += length;
}
if (actualIndex < 0 || actualIndex >= length) {
throw Starlark.errorf(
"index out of range (index is %d, but sequence has %d elements)", index, length);
}
return actualIndex;
}
/**
* Returns the effective index denoted by a user-supplied integer. First, if the integer is
* negative, the length of the sequence is added to it, so an index of -1 represents the last
* element of the sequence. Then, the integer is "clamped" into the inclusive interval [0,
* length].
*/
static int toIndex(int index, int length) {
if (index < 0) {
index += length;
}
if (index < 0) {
return 0;
} else if (index > length) {
return length;
} else {
return index;
}
}
/** Evaluates an eager binary operation, {@code x op y}. (Excludes AND and OR.) */
static Object binaryOp(TokenKind op, Object x, Object y, StarlarkThread starlarkThread)
throws EvalException {
StarlarkSemantics semantics = starlarkThread.getSemantics();
Mutability mu = starlarkThread.mutability();
switch (op) {
case PLUS:
if (x instanceof StarlarkInt) {
if (y instanceof StarlarkInt) {
// int + int
return StarlarkInt.add((StarlarkInt) x, (StarlarkInt) y);
} else if (y instanceof StarlarkFloat) {
// int + float
double z = ((StarlarkInt) x).toFiniteDouble() + ((StarlarkFloat) y).toDouble();
return StarlarkFloat.of(z);
}
} else if (x instanceof String) {
if (y instanceof String) {
// string + string
return (String) x + (String) y;
}
} else if (x instanceof Tuple) {
if (y instanceof Tuple) {
// tuple + tuple
return Tuple.concat((Tuple) x, (Tuple) y);
}
} else if (x instanceof StarlarkList) {
if (y instanceof StarlarkList) {
// list + list
return StarlarkList.concat((StarlarkList<?>) x, (StarlarkList<?>) y, mu);
}
} else if (x instanceof StarlarkFloat) {
double xf = ((StarlarkFloat) x).toDouble();
if (y instanceof StarlarkFloat) {
// float + float
double z = xf + ((StarlarkFloat) y).toDouble();
return StarlarkFloat.of(z);
} else if (y instanceof StarlarkInt) {
// float + int
double z = xf + ((StarlarkInt) y).toFiniteDouble();
return StarlarkFloat.of(z);
}
}
break;
case PIPE:
if (x instanceof StarlarkInt) {
if (y instanceof StarlarkInt) {
// int | int
return StarlarkInt.or((StarlarkInt) x, (StarlarkInt) y);
}
} else if (x instanceof Map) {
if (y instanceof Map) {
// map | map (usually dicts)
return Dict.builder().putAll((Map<?, ?>) x).putAll((Map<?, ?>) y).build(mu);
}
}
break;
case AMPERSAND:
if (x instanceof StarlarkInt && y instanceof StarlarkInt) {
// int & int
return StarlarkInt.and((StarlarkInt) x, (StarlarkInt) y);
}
break;
case CARET:
if (x instanceof StarlarkInt && y instanceof StarlarkInt) {
// int ^ int
return StarlarkInt.xor((StarlarkInt) x, (StarlarkInt) y);
}
break;
case GREATER_GREATER:
if (x instanceof StarlarkInt && y instanceof StarlarkInt) {
// x >> y
return StarlarkInt.shiftRight((StarlarkInt) x, (StarlarkInt) y);
}
break;
case LESS_LESS:
if (x instanceof StarlarkInt && y instanceof StarlarkInt) {
// x << y
return StarlarkInt.shiftLeft((StarlarkInt) x, (StarlarkInt) y);
}
break;
case MINUS:
if (x instanceof StarlarkInt) {
if (y instanceof StarlarkInt) {
// int - int
return StarlarkInt.subtract((StarlarkInt) x, (StarlarkInt) y);
} else if (y instanceof StarlarkFloat) {
// int - float
double z = ((StarlarkInt) x).toFiniteDouble() - ((StarlarkFloat) y).toDouble();
return StarlarkFloat.of(z);
}
} else if (x instanceof StarlarkFloat) {
double xf = ((StarlarkFloat) x).toDouble();
if (y instanceof StarlarkFloat) {
// float - float
double z = xf - ((StarlarkFloat) y).toDouble();
return StarlarkFloat.of(z);
} else if (y instanceof StarlarkInt) {
// float - int
double z = xf - ((StarlarkInt) y).toFiniteDouble();
return StarlarkFloat.of(z);
}
}
break;
case STAR:
if (x instanceof StarlarkInt) {
StarlarkInt xi = (StarlarkInt) x;
if (y instanceof StarlarkInt) {
// int * int
return StarlarkInt.multiply(xi, (StarlarkInt) y);
} else if (y instanceof String) {
// int * string
return repeatString((String) y, xi);
} else if (y instanceof Tuple) {
// int * tuple
return ((Tuple) y).repeat(xi);
} else if (y instanceof StarlarkList) {
// int * list
return ((StarlarkList<?>) y).repeat(xi, mu);
} else if (y instanceof StarlarkFloat) {
// int * float
double z = xi.toFiniteDouble() * ((StarlarkFloat) y).toDouble();
return StarlarkFloat.of(z);
}
} else if (x instanceof String) {
if (y instanceof StarlarkInt) {
// string * int
return repeatString((String) x, (StarlarkInt) y);
}
} else if (x instanceof Tuple) {
if (y instanceof StarlarkInt) {
// tuple * int
return ((Tuple) x).repeat((StarlarkInt) y);
}
} else if (x instanceof StarlarkList) {
if (y instanceof StarlarkInt) {
// list * int
return ((StarlarkList<?>) x).repeat((StarlarkInt) y, mu);
}
} else if (x instanceof StarlarkFloat) {
double xf = ((StarlarkFloat) x).toDouble();
if (y instanceof StarlarkFloat) {
// float * float
return StarlarkFloat.of(xf * ((StarlarkFloat) y).toDouble());
} else if (y instanceof StarlarkInt) {
// float * int
return StarlarkFloat.of(xf * ((StarlarkInt) y).toFiniteDouble());
}
}
break;
case SLASH: // real division
if (x instanceof StarlarkInt) {
double xf = ((StarlarkInt) x).toFiniteDouble();
if (y instanceof StarlarkInt) {
// int / int
return StarlarkFloat.div(xf, ((StarlarkInt) y).toFiniteDouble());
} else if (y instanceof StarlarkFloat) {
// int / float
return StarlarkFloat.div(xf, ((StarlarkFloat) y).toDouble());
}
} else if (x instanceof StarlarkFloat) {
double xf = ((StarlarkFloat) x).toDouble();
if (y instanceof StarlarkFloat) {
// float / float
return StarlarkFloat.div(xf, ((StarlarkFloat) y).toDouble());
} else if (y instanceof StarlarkInt) {
// float / int
return StarlarkFloat.div(xf, ((StarlarkInt) y).toFiniteDouble());
}
}
break;
case SLASH_SLASH:
if (x instanceof StarlarkInt) {
if (y instanceof StarlarkInt) {
// int // int
return StarlarkInt.floordiv((StarlarkInt) x, (StarlarkInt) y);
} else if (y instanceof StarlarkFloat) {
// int // float
double xf = ((StarlarkInt) x).toFiniteDouble();
double yf = ((StarlarkFloat) y).toDouble();
return StarlarkFloat.floordiv(xf, yf);
}
} else if (x instanceof StarlarkFloat) {
double xf = ((StarlarkFloat) x).toDouble();
if (y instanceof StarlarkFloat) {
// float // float
return StarlarkFloat.floordiv(xf, ((StarlarkFloat) y).toDouble());
} else if (y instanceof StarlarkInt) {
// float // int
return StarlarkFloat.floordiv(xf, ((StarlarkInt) y).toFiniteDouble());
}
}
break;
case PERCENT:
if (x instanceof StarlarkInt) {
if (y instanceof StarlarkInt) {
// int % int
return StarlarkInt.mod((StarlarkInt) x, (StarlarkInt) y);
} else if (y instanceof StarlarkFloat) {
// int % float
double xf = ((StarlarkInt) x).toFiniteDouble();
double yf = ((StarlarkFloat) y).toDouble();
return StarlarkFloat.mod(xf, yf);
}
} else if (x instanceof String) {
// string % any
String xs = (String) x;
try {
if (y instanceof Tuple) {
return Starlark.formatWithList(semantics, xs, (Tuple) y);
} else {
return Starlark.format(semantics, xs, y);
}
} catch (IllegalFormatException ex) {
throw new EvalException(ex);
}
} else if (x instanceof StarlarkFloat) {
double xf = ((StarlarkFloat) x).toDouble();
if (y instanceof StarlarkFloat) {
// float % float
return StarlarkFloat.mod(xf, ((StarlarkFloat) y).toDouble());
} else if (y instanceof StarlarkInt) {
// float % int
return StarlarkFloat.mod(xf, ((StarlarkInt) y).toFiniteDouble());
}
}
break;
case EQUALS_EQUALS:
return x.equals(y);
case NOT_EQUALS:
return !x.equals(y);
case LESS:
return compare(x, y) < 0;
case LESS_EQUALS:
return compare(x, y) <= 0;
case GREATER:
return compare(x, y) > 0;
case GREATER_EQUALS:
return compare(x, y) >= 0;
case IN:
if (y instanceof StarlarkIndexable) {
return ((StarlarkIndexable) y).containsKey(semantics, x);
} else if (y instanceof StarlarkIndexable.Threaded) {
return ((StarlarkIndexable.Threaded) y).containsKey(starlarkThread, semantics, x);
} else if (y instanceof String) {
if (!(x instanceof String)) {
throw Starlark.errorf(
"'in <string>' requires string as left operand, not '%s'", Starlark.type(x));
}
return ((String) y).contains((String) x);
}
break;
case NOT_IN:
Object z = binaryOp(TokenKind.IN, x, y, starlarkThread);
if (z != null) {
return !Starlark.truth(z);
}
break;
default:
throw new AssertionError("not a binary operator: " + op);
}
// custom binary operator?
if (x instanceof HasBinary) {
Object z = ((HasBinary) x).binaryOp(op, y, true);
if (z != null) {
return z;
}
}
if (y instanceof HasBinary) {
Object z = ((HasBinary) y).binaryOp(op, x, false);
if (z != null) {
return z;
}
}
throw Starlark.errorf(
"unsupported binary operation: %s %s %s", Starlark.type(x), op, Starlark.type(y));
}
// Defines the behavior of the language's ordered comparison operators (< <= => >).
private static int compare(Object x, Object y) throws EvalException {
try {
return Starlark.compareUnchecked(x, y);
} catch (ClassCastException ex) {
throw new EvalException(ex.getMessage());
}
}
private static String repeatString(String s, StarlarkInt in) throws EvalException {
int n = in.toInt("repeat");
if (n <= 0) {
return "";
} else if ((long) s.length() * (long) n > Integer.MAX_VALUE) {
// Would exceed max length of a java String.
throw Starlark.errorf("excessive repeat (%d * %d characters)", s.length(), n);
} else {
return s.repeat(n);
}
}
/** Evaluates a unary operation. */
static Object unaryOp(TokenKind op, Object x) throws EvalException {
switch (op) {
case NOT:
return !Starlark.truth(x);
case MINUS:
if (x instanceof StarlarkInt) {
return StarlarkInt.uminus((StarlarkInt) x); // -int
} else if (x instanceof StarlarkFloat) {
return StarlarkFloat.of(-((StarlarkFloat) x).toDouble()); // -float
}
break;
case PLUS:
if (x instanceof StarlarkInt) {
return x; // +int
} else if (x instanceof StarlarkFloat) {
return x; // +float
}
break;
case TILDE:
if (x instanceof StarlarkInt) {
return StarlarkInt.bitnot((StarlarkInt) x); // ~int
}
break;
default:
/* fall through */
}
throw Starlark.errorf("unsupported unary operation: %s%s", op, Starlark.type(x));
}
/**
* Returns the element of sequence or mapping {@code object} indexed by {@code key}.
*
* @throws EvalException if {@code object} is not a sequence or mapping.
*/
@Nullable
static Object index(StarlarkThread starlarkThread, Object object, Object key)
throws EvalException {
Mutability mu = starlarkThread.mutability();
StarlarkSemantics semantics = starlarkThread.getSemantics();
if (object instanceof StarlarkIndexable.Threaded) {
return ((StarlarkIndexable.Threaded) object).getIndex(starlarkThread, semantics, key);
} else if (object instanceof StarlarkIndexable) {
Object result = ((StarlarkIndexable) object).getIndex(semantics, key);
// TODO(bazel-team): We shouldn't have this fromJava call here. If it's needed at all,
// it should go in the implementations of StarlarkIndexable#getIndex that produce non-Starlark
// values.
return result == null ? null : Starlark.fromJava(result, mu);
} else if (object instanceof String) {
String string = (String) object;
int index = Starlark.toInt(key, "string index");
index = getSequenceIndex(index, string.length());
return StringModule.memoizedCharToString(string.charAt(index));
} else {
throw Starlark.errorf(
"type '%s' has no operator [](%s)", Starlark.type(object), Starlark.type(key));
}
}
/**
* Updates an object as if by the Starlark statement {@code object[key] = value}.
*
* @throws EvalException if the object is not a list or dict.
*/
static void setIndex(Object object, Object key, Object value) throws EvalException {
if (object instanceof Dict) {
@SuppressWarnings("unchecked")
Dict<Object, Object> dict = (Dict<Object, Object>) object;
dict.putEntry(key, value);
} else if (object instanceof StarlarkList) {
@SuppressWarnings("unchecked")
StarlarkList<Object> list = (StarlarkList<Object>) object;
int index = Starlark.toInt(key, "list index");
index = EvalUtils.getSequenceIndex(index, list.size());
list.setElementAt(index, value);
} else {
throw Starlark.errorf(
"can only assign an element in a dictionary or a list, not in a '%s'",
Starlark.type(object));
}
}
/** Updates the named field of x as if by the Starlark statement {@code x.field = value}. */
static void setField(Object x, String field, Object value) throws EvalException {
if (x instanceof Structure) {
((Structure) x).setField(field, value);
} else {
throw Starlark.errorf("cannot set .%s field of %s value", field, Starlark.type(x));
}
}
}