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