| // 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)); | |
| } | |
| } | |
| } |