| // 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 static com.google.common.collect.Streams.stream; |
| import static java.util.Comparator.comparing; |
| |
| import com.google.common.base.Ascii; |
| import com.google.common.base.Throwables; |
| import com.google.common.collect.Ordering; |
| import java.util.Arrays; |
| import java.util.Comparator; |
| import java.util.Iterator; |
| import java.util.NoSuchElementException; |
| import java.util.Optional; |
| import net.starlark.java.annot.Param; |
| import net.starlark.java.annot.ParamType; |
| import net.starlark.java.annot.StarlarkBuiltin; |
| import net.starlark.java.annot.StarlarkMethod; |
| |
| /** The universal predeclared functions of core Starlark. */ |
| class MethodLibrary { |
| |
| @StarlarkMethod( |
| name = "min", |
| doc = |
| "Returns the smallest one of all given arguments. If only one positional argument is" |
| + " provided, it must be a non-empty iterable. It is an error if elements are not" |
| + " comparable (for example int with string), or if no arguments are given." |
| + "<pre class=\"language-python\">\n" // |
| + "min(2, 5, 4) == 2\n" |
| + "min([5, 6, 3]) == 3\n" |
| + "min(\"six\", \"three\", \"four\", key = len) == \"six\" # the shortest\n" |
| + "min([2, -2, -1, 1], key = abs) == -1 # the first encountered with minimal key" |
| + " value\n" |
| + "</pre>", |
| extraPositionals = @Param(name = "args", doc = "The elements to be checked."), |
| parameters = { |
| @Param( |
| name = "key", |
| named = true, |
| positional = false, |
| allowedTypes = { |
| @ParamType(type = StarlarkCallable.class), |
| @ParamType(type = NoneType.class), |
| }, |
| doc = "An optional function applied to each element before comparison.", |
| defaultValue = "None") |
| }, |
| useStarlarkThread = true) |
| public Object min(Object key, Sequence<?> args, StarlarkThread thread) |
| throws EvalException, InterruptedException { |
| return findExtreme( |
| args, |
| Starlark.toJavaOptional(key, StarlarkCallable.class), |
| Starlark.ORDERING.reverse(), |
| thread); |
| } |
| |
| @StarlarkMethod( |
| name = "max", |
| doc = |
| "Returns the largest one of all given arguments. If only one positional argument is" |
| + " provided, it must be a non-empty iterable.It is an error if elements are not" |
| + " comparable (for example int with string), or if no arguments are given." |
| + "<pre class=\"language-python\">\n" // |
| + "max(2, 5, 4) == 5\n" |
| + "max([5, 6, 3]) == 6\n" |
| + "max(\"two\", \"three\", \"four\", key = len) ==\"three\" # the longest\n" |
| + "max([1, -1, -2, 2], key = abs) == -2 # the first encountered with maximal key" |
| + " value\n" |
| + "</pre>", |
| extraPositionals = @Param(name = "args", doc = "The elements to be checked."), |
| parameters = { |
| @Param( |
| name = "key", |
| named = true, |
| positional = false, |
| allowedTypes = { |
| @ParamType(type = StarlarkCallable.class), |
| @ParamType(type = NoneType.class), |
| }, |
| doc = "An optional function applied to each element before comparison.", |
| defaultValue = "None") |
| }, |
| useStarlarkThread = true) |
| public Object max(Object key, Sequence<?> args, StarlarkThread thread) |
| throws EvalException, InterruptedException { |
| return findExtreme( |
| args, Starlark.toJavaOptional(key, StarlarkCallable.class), Starlark.ORDERING, thread); |
| } |
| |
| /** Returns the maximum element from this list, as determined by maxOrdering. */ |
| private static Object findExtreme( |
| Sequence<?> args, |
| Optional<StarlarkCallable> keyFn, |
| Ordering<Object> maxOrdering, |
| StarlarkThread thread) |
| throws EvalException, InterruptedException { |
| // Args can either be a list of items to compare, or a singleton list whose element is an |
| // iterable of items to compare. In either case, there must be at least one item to compare. |
| Iterable<?> items = (args.size() == 1) ? Starlark.toIterable(args.get(0)) : args; |
| try { |
| if (keyFn.isPresent()) { |
| try { |
| return stream(items) |
| .map(value -> ValueWithComparisonKey.make(value, keyFn.get(), thread)) |
| .max(comparing(ValueWithComparisonKey::getComparisonKey, maxOrdering)) |
| .get() |
| .getValue(); |
| } catch (ValueWithComparisonKey.KeyCallException ex) { |
| Throwables.throwIfInstanceOf(ex.getCause(), EvalException.class); |
| Throwables.throwIfInstanceOf(ex.getCause(), InterruptedException.class); |
| throw new AssertionError("Got invalid ValueWithComparisonKey.KeyCallException", ex); |
| } |
| } else { |
| return maxOrdering.max(items); |
| } |
| } catch (ClassCastException ex) { |
| throw new EvalException(ex.getMessage()); // e.g. unsupported comparison: int <=> string |
| } catch (NoSuchElementException ex) { |
| throw new EvalException("expected at least one item", ex); |
| } |
| } |
| |
| /** |
| * Original value decorated with its comparison key; storing the comparison key alongside the |
| * value ensures that we call the comparison key computation function only once per original value |
| * (which is important in case the function has side effects). |
| */ |
| private static final class ValueWithComparisonKey { |
| private final Object value; |
| private final Object comparisonKey; |
| |
| private ValueWithComparisonKey(Object value, Object comparisonKey) { |
| this.value = value; |
| this.comparisonKey = comparisonKey; |
| } |
| |
| /** |
| * @throws KeyCallException wrapping the exception thrown by the underlying {@link |
| * Starlark#fastcall} call if it threw. |
| */ |
| static ValueWithComparisonKey make( |
| Object value, StarlarkCallable keyFn, StarlarkThread thread) { |
| Object[] positional = {value}; |
| Object[] named = {}; |
| try { |
| return new ValueWithComparisonKey( |
| value, Starlark.fastcall(thread, keyFn, positional, named)); |
| } catch (EvalException | InterruptedException ex) { |
| throw new KeyCallException(ex); |
| } |
| } |
| |
| Object getValue() { |
| return value; |
| } |
| |
| Object getComparisonKey() { |
| return comparisonKey; |
| } |
| |
| /** An unchecked exception wrapping an exception thrown by {@link Starlark#fastcall}. */ |
| private static final class KeyCallException extends RuntimeException { |
| KeyCallException(Exception cause) { |
| super(cause); |
| } |
| } |
| } |
| |
| @StarlarkMethod( |
| name = "abs", |
| doc = |
| "Returns the absolute value of a number (a non-negative number with the same magnitude)." |
| + "<pre class=\"language-python\">abs(-2.3) == 2.3</pre>", |
| parameters = { |
| @Param( |
| name = "x", |
| allowedTypes = { |
| @ParamType(type = StarlarkInt.class), |
| @ParamType(type = StarlarkFloat.class), |
| }, |
| doc = "A number (int or float)") |
| }) |
| public Object abs(Object x) throws EvalException { |
| if (x instanceof StarlarkInt) { |
| StarlarkInt starlarkInt = (StarlarkInt) x; |
| if (starlarkInt.signum() < 0) { |
| return StarlarkInt.uminus(starlarkInt); |
| } |
| return x; |
| } |
| |
| double value = ((StarlarkFloat) x).toDouble(); |
| return StarlarkFloat.of(Math.abs(value)); |
| } |
| |
| @StarlarkMethod( |
| name = "all", |
| doc = |
| "Returns true if all elements evaluate to True or if the collection is empty. " |
| + "Elements are converted to boolean using the <a href=\"#bool\">bool</a> function." |
| + "<pre class=\"language-python\">all([\"hello\", 3, True]) == True\n" |
| + "all([-1, 0, 1]) == False</pre>", |
| parameters = {@Param(name = "elements", doc = "A string or a collection of elements.")}) |
| public boolean all(Object collection) throws EvalException { |
| return !hasElementWithBooleanValue(collection, false); |
| } |
| |
| @StarlarkMethod( |
| name = "any", |
| doc = |
| "Returns true if at least one element evaluates to True. " |
| + "Elements are converted to boolean using the <a href=\"#bool\">bool</a> function." |
| + "<pre class=\"language-python\">any([-1, 0, 1]) == True\n" |
| + "any([False, 0, \"\"]) == False</pre>", |
| parameters = {@Param(name = "elements", doc = "A string or a collection of elements.")}) |
| public boolean any(Object collection) throws EvalException { |
| return hasElementWithBooleanValue(collection, true); |
| } |
| |
| private static boolean hasElementWithBooleanValue(Object seq, boolean value) |
| throws EvalException { |
| for (Object x : Starlark.toIterable(seq)) { |
| if (Starlark.truth(x) == value) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| @StarlarkMethod( |
| name = "sorted", |
| doc = |
| "Returns a new sorted list containing all the elements of the supplied iterable" |
| + " sequence. An error may occur if any pair of elements x, y may not be compared" |
| + " using x < y. The elements are sorted into ascending order, unless the reverse" |
| + " argument is True, in which case the order is descending.\n" |
| + " Sorting is stable: elements that compare equal retain their original relative" |
| + " order.\n" // |
| + "<pre class=\"language-python\">\n" // |
| + "sorted([3, 5, 4]) == [3, 4, 5]\n" // |
| + "sorted([3, 5, 4], reverse = True) == [5, 4, 3]\n" // |
| + "sorted([\"two\", \"three\", \"four\"], key = len) == [\"two\", \"four\"," |
| + " \"three\"] # sort by length\n" // |
| + "</pre>", |
| parameters = { |
| @Param(name = "iterable", doc = "The iterable sequence to sort."), |
| @Param( |
| name = "key", |
| named = true, |
| allowedTypes = { |
| @ParamType(type = StarlarkCallable.class), |
| @ParamType(type = NoneType.class), |
| }, |
| doc = "An optional function applied to each element before comparison.", |
| defaultValue = "None"), |
| @Param( |
| name = "reverse", |
| doc = "Return results in descending order.", |
| named = true, |
| defaultValue = "False", |
| positional = false) |
| }, |
| useStarlarkThread = true) |
| public StarlarkList<?> sorted( |
| StarlarkIterable<?> iterable, Object key, boolean reverse, StarlarkThread thread) |
| throws EvalException, InterruptedException { |
| Object[] array = Starlark.toArray(iterable); |
| Comparator<Object> order = reverse ? Starlark.ORDERING.reversed() : Starlark.ORDERING; |
| |
| // no key? |
| if (key == Starlark.NONE) { |
| try { |
| Arrays.sort(array, order); |
| } catch (ClassCastException ex) { |
| throw Starlark.errorf("%s", ex.getMessage()); |
| } |
| return StarlarkList.wrap(thread.mutability(), array); |
| } |
| |
| // The user provided a key function. |
| // We must call it exactly once per element, in order, |
| // so use the decorate/sort/undecorate pattern. |
| StarlarkCallable keyfn = (StarlarkCallable) key; |
| |
| // decorate |
| Object[] empty = {}; |
| for (int i = 0; i < array.length; i++) { |
| Object v = array[i]; |
| Object k = Starlark.fastcall(thread, keyfn, new Object[] {v}, empty); |
| array[i] = new Object[] {k, v}; |
| } |
| |
| class KeyComparator implements Comparator<Object> { |
| EvalException e; |
| |
| @Override |
| public int compare(Object x, Object y) { |
| Object xkey = ((Object[]) x)[0]; |
| Object ykey = ((Object[]) y)[0]; |
| try { |
| return order.compare(xkey, ykey); |
| } catch (ClassCastException e) { |
| if (this.e == null) { |
| this.e = new EvalException(e.getMessage()); |
| } |
| return 0; // may cause Arrays.sort to fail; see below |
| } |
| } |
| } |
| |
| // sort |
| KeyComparator comp = new KeyComparator(); |
| try { |
| Arrays.sort(array, comp); |
| } catch (IllegalArgumentException unused) { |
| // Arrays.sort failed because comp violated the Comparator contract. |
| if (comp.e == null) { |
| // There was no exception from order.compare. |
| // Likely the application defined a Comparable type whose |
| // compareTo is not a strict weak order. |
| throw new IllegalStateException("sort: element ordering is not self-consistent"); |
| } |
| } |
| |
| // Sort completed, possibly with deferred errors. |
| if (comp.e != null) { |
| throw comp.e; |
| } |
| |
| // undecorate |
| for (int i = 0; i < array.length; i++) { |
| array[i] = ((Object[]) array[i])[1]; |
| } |
| |
| return StarlarkList.wrap(thread.mutability(), array); |
| } |
| |
| private static void reverse(Object[] array) { |
| for (int i = 0, j = array.length - 1; i < j; i++, j--) { |
| Object tmp = array[i]; |
| array[i] = array[j]; |
| array[j] = tmp; |
| } |
| } |
| |
| @StarlarkMethod( |
| name = "reversed", |
| doc = |
| "Returns a new, unfrozen list that contains the elements of the original iterable" |
| + " sequence in reversed order.<pre class=\"language-python\">reversed([3, 5, 4]) ==" |
| + " [4, 5, 3]</pre>", |
| parameters = { |
| @Param(name = "sequence", doc = "The iterable sequence (e.g. list) to be reversed."), |
| }, |
| useStarlarkThread = true) |
| public StarlarkList<?> reversed(StarlarkIterable<?> sequence, StarlarkThread thread) |
| throws EvalException { |
| Object[] array = Starlark.toArray(sequence); |
| reverse(array); |
| return StarlarkList.wrap(thread.mutability(), array); |
| } |
| |
| @StarlarkMethod( |
| name = "tuple", |
| doc = |
| "Returns a tuple with the same elements as the given iterable value." |
| + "<pre class=\"language-python\">tuple([1, 2]) == (1, 2)\n" |
| + "tuple((2, 3, 2)) == (2, 3, 2)\n" |
| + "tuple({5: \"a\", 2: \"b\", 4: \"c\"}) == (5, 2, 4)</pre>", |
| parameters = {@Param(name = "x", defaultValue = "()", doc = "The object to convert.")}) |
| public Tuple tuple(StarlarkIterable<?> x) throws EvalException { |
| if (x instanceof Tuple) { |
| return (Tuple) x; |
| } |
| return Tuple.wrap(Starlark.toArray(x)); |
| } |
| |
| @StarlarkMethod( |
| name = "list", |
| doc = |
| "Returns a new list with the same elements as the given iterable value." |
| + "<pre class=\"language-python\">list([1, 2]) == [1, 2]\n" |
| + "list((2, 3, 2)) == [2, 3, 2]\n" |
| + "list({5: \"a\", 2: \"b\", 4: \"c\"}) == [5, 2, 4]</pre>", |
| parameters = {@Param(name = "x", defaultValue = "[]", doc = "The object to convert.")}, |
| useStarlarkThread = true) |
| public StarlarkList<?> list(StarlarkIterable<?> x, StarlarkThread thread) throws EvalException { |
| return StarlarkList.wrap(thread.mutability(), Starlark.toArray(x)); |
| } |
| |
| @StarlarkMethod( |
| name = "len", |
| doc = |
| "Returns the length of a string, sequence (such as a list or tuple), dict, or other" |
| + " iterable.", |
| parameters = {@Param(name = "x", doc = "The value whose length to report.")}, |
| useStarlarkThread = true) |
| public int len(Object x, StarlarkThread thread) throws EvalException { |
| int len = Starlark.len(x); |
| if (len < 0) { |
| throw Starlark.errorf("%s is not iterable", Starlark.type(x)); |
| } |
| return len; |
| } |
| |
| @StarlarkMethod( |
| name = "str", |
| doc = |
| "Converts any object to string. This is useful for debugging." |
| + "<pre class=\"language-python\">str(\"ab\") == \"ab\"\n" |
| + "str(8) == \"8\"</pre>", |
| parameters = {@Param(name = "x", doc = "The object to convert.")}, |
| useStarlarkThread = true) |
| public String str(Object x, StarlarkThread thread) throws EvalException { |
| return Starlark.str(x, thread.getSemantics()); |
| } |
| |
| @StarlarkMethod( |
| name = "repr", |
| doc = |
| "Converts any object to a string representation. This is useful for debugging.<br>" |
| + "<pre class=\"language-python\">repr(\"ab\") == '\"ab\"'</pre>", |
| parameters = {@Param(name = "x", doc = "The object to convert.")}) |
| public String repr(Object x) { |
| return Starlark.repr(x); |
| } |
| |
| @StarlarkMethod( |
| name = "bool", |
| doc = |
| "Constructor for the bool type. " |
| + "It returns <code>False</code> if the object is <code>None</code>, <code>False" |
| + "</code>, an empty string (<code>\"\"</code>), the number <code>0</code>, or an " |
| + "empty collection (e.g. <code>()</code>, <code>[]</code>). " |
| + "Otherwise, it returns <code>True</code>.", |
| parameters = {@Param(name = "x", defaultValue = "False", doc = "The variable to convert.")}) |
| public boolean bool(Object x) throws EvalException { |
| return Starlark.truth(x); |
| } |
| |
| @StarlarkMethod( |
| name = "float", |
| doc = |
| "Returns x as a float value. " // |
| + "<ul><li>If <code>x</code> is already a float, <code>float</code> returns it" |
| + " unchanged. " // |
| + "<li>If <code>x</code> is a bool, <code>float</code> returns 1.0 for True and 0.0" |
| + " for False. " // |
| + "<li>If <code>x</code> is an int, <code>float</code> returns the nearest" |
| + " finite floating-point value to x, or an error if the magnitude is too large. " // |
| + "<li>If <code>x</code> is a string, it must be a valid floating-point literal, or" |
| + " be equal (ignoring case) to <code>NaN</code>, <code>Inf</code>, or" |
| + " <code>Infinity</code>, optionally preceded by a <code>+</code> or <code>-</code>" |
| + " sign. " // |
| + "</ul>" // |
| + "Any other value causes an error. With no argument, <code>float()</code> returns" |
| + " 0.0.", |
| parameters = { |
| @Param(name = "x", doc = "The value to convert.", defaultValue = "unbound"), |
| }) |
| public StarlarkFloat floatForStarlark(Object x) throws EvalException { |
| if (x instanceof String) { |
| String s = (String) x; |
| if (s.isEmpty()) { |
| throw Starlark.errorf("empty string"); |
| } |
| |
| double d; |
| switch (Ascii.toLowerCase(s.charAt(s.length() - 1))) { |
| case 'n': |
| case 'f': |
| case 'y': // {,+,-}{NaN,Inf,Infinity} |
| // non-finite |
| if (Ascii.equalsIgnoreCase(s, "nan") |
| || Ascii.equalsIgnoreCase(s, "+nan") |
| || Ascii.equalsIgnoreCase(s, "-nan")) { |
| d = Double.NaN; |
| } else if (Ascii.equalsIgnoreCase(s, "inf") |
| || Ascii.equalsIgnoreCase(s, "+inf") |
| || Ascii.equalsIgnoreCase(s, "+infinity")) { |
| d = Double.POSITIVE_INFINITY; |
| } else if (Ascii.equalsIgnoreCase(s, "-inf") || Ascii.equalsIgnoreCase(s, "-infinity")) { |
| d = Double.NEGATIVE_INFINITY; |
| } else { |
| throw Starlark.errorf("invalid float literal: %s", s); |
| } |
| break; |
| default: |
| // finite |
| try { |
| d = Double.parseDouble(s); |
| if (!Double.isFinite(d)) { |
| // parseDouble accepts signed "NaN" and "Infinity" (case sensitive) |
| // but we already handled those cases, so this indicates |
| // a large number rounded to infinity. |
| throw Starlark.errorf("floating-point number too large"); |
| } |
| } catch (NumberFormatException unused) { |
| throw Starlark.errorf("invalid float literal: %s", s); |
| } |
| break; |
| } // switch |
| return StarlarkFloat.of(d); |
| |
| } else if (x instanceof Boolean) { |
| return StarlarkFloat.of(((Boolean) x).booleanValue() ? 1 : 0); |
| |
| } else if (x instanceof StarlarkInt) { |
| return StarlarkFloat.of(((StarlarkInt) x).toFiniteDouble()); |
| |
| } else if (x instanceof StarlarkFloat) { |
| return (StarlarkFloat) x; |
| |
| } else if (x == Starlark.UNBOUND) { |
| return StarlarkFloat.of(0.0); |
| |
| } else { |
| throw Starlark.errorf("got %s, want string, int, float, or bool", Starlark.type(x)); |
| } |
| } |
| |
| @StarlarkMethod( |
| name = "int", |
| doc = |
| "Returns x as an int value." |
| + "<ul>" |
| + "<li>If <code>x</code> is already an int, <code>int</code> returns it unchanged." // |
| + "<li>If <code>x</code> is a bool, <code>int</code> returns 1 for True and 0 for" |
| + " False." // |
| + "<li>If <code>x</code> is a string, it must have the format " |
| + " <code><sign><prefix><digits></code>. " |
| + " <code><sign></code> is either <code>\"+\"</code>, <code>\"-\"</code>, " |
| + " or empty (interpreted as positive). <code><digits></code> are a " |
| + " sequence of digits from 0 up to <code>base</code> - 1, where the letters a-z " |
| + " (or equivalently, A-Z) are used as digits for 10-35. In the case where " |
| + " <code>base</code> is 2/8/16, <code><prefix></code> is optional and may " |
| + " be 0b/0o/0x (or equivalently, 0B/0O/0X) respectively; if the " |
| + " <code>base</code> is any other value besides these bases or the special value " |
| + " 0, the prefix must be empty. In the case where <code>base</code> is 0, the " |
| + " string is interpreted as an integer literal, in the sense that one of the " |
| + " bases 2/8/10/16 is chosen depending on which prefix if any is used. If " |
| + " <code>base</code> is 0, no prefix is used, and there is more than one digit, " |
| + " the leading digit cannot be 0; this is to avoid confusion between octal and " |
| + " decimal. The magnitude of the number represented by the string must be within " |
| + " the allowed range for the int type." // |
| + "<li>If <code>x</code> is a float, <code>int</code> returns the integer value of" |
| + " the float, rounding towards zero. It is an error if x is non-finite (NaN or" |
| + " infinity)." |
| + "</ul>" // |
| + "This function fails if <code>x</code> is any other type, or if the value is a " |
| + "string not satisfying the above format. Unlike Python's <code>int</code> " |
| + "function, this function does not allow zero arguments, and does " |
| + "not allow extraneous whitespace for string arguments.<p>" // |
| + "Examples:<pre class=\"language-python\">int(\"123\") == 123\n" |
| + "int(\"-123\") == -123\n" |
| + "int(\"+123\") == 123\n" |
| + "int(\"FF\", 16) == 255\n" |
| + "int(\"0xFF\", 16) == 255\n" |
| + "int(\"10\", 0) == 10\n" |
| + "int(\"-0x10\", 0) == -16\n" |
| + "int(\"-0x10\", 0) == -16\n" |
| + "int(\"123.456\") == 123\n" |
| + "</pre>", |
| parameters = { |
| @Param(name = "x", doc = "The string to convert."), |
| @Param( |
| name = "base", |
| defaultValue = "unbound", |
| doc = |
| "The base used to interpret a string value; defaults to 10. Must be between 2 " |
| + "and 36 (inclusive), or 0 to detect the base as if <code>x</code> were an " |
| + "integer literal. This parameter must not be supplied if the value is not a " |
| + "string.", |
| named = true) |
| }) |
| public StarlarkInt intForStarlark(Object x, Object baseO) throws EvalException { |
| if (x instanceof String) { |
| int base = baseO == Starlark.UNBOUND ? 10 : Starlark.toInt(baseO, "base"); |
| try { |
| return StarlarkInt.parse((String) x, base); |
| } catch (NumberFormatException ex) { |
| throw Starlark.errorf("%s", ex.getMessage()); |
| } |
| } |
| |
| if (baseO != Starlark.UNBOUND) { |
| throw Starlark.errorf("can't convert non-string with explicit base"); |
| } |
| if (x instanceof Boolean) { |
| return StarlarkInt.of(((Boolean) x).booleanValue() ? 1 : 0); |
| } else if (x instanceof StarlarkInt) { |
| return (StarlarkInt) x; |
| } else if (x instanceof StarlarkFloat) { |
| try { |
| return StarlarkInt.ofFiniteDouble(((StarlarkFloat) x).toDouble()); |
| } catch (IllegalArgumentException unused) { |
| throw Starlark.errorf("can't convert float %s to int", x); |
| } |
| } |
| throw Starlark.errorf("got %s, want string, int, float, or bool", Starlark.type(x)); |
| } |
| |
| @StarlarkMethod( |
| name = "dict", |
| doc = |
| "Creates a <a href=\"../core/dict.html\">dictionary</a> from an optional positional " |
| + "argument and an optional set of keyword arguments. In the case where the same key " |
| + "is given multiple times, the last value will be used. Entries supplied via " |
| + "keyword arguments are considered to come after entries supplied via the " |
| + "positional argument.", |
| parameters = { |
| @Param( |
| name = "pairs", |
| defaultValue = "[]", |
| doc = "A dict, or an iterable whose elements are each of length 2 (key, value)."), |
| }, |
| extraKeywords = @Param(name = "kwargs", doc = "Dictionary of additional entries."), |
| useStarlarkThread = true) |
| public Dict<?, ?> dict(Object pairs, Dict<String, Object> kwargs, StarlarkThread thread) |
| throws EvalException { |
| // common case: dict(k=v, ...) |
| if (pairs instanceof StarlarkList && ((StarlarkList) pairs).isEmpty()) { |
| return kwargs; |
| } |
| Dict<Object, Object> dict = Dict.of(thread.mutability()); |
| Dict.update("dict", dict, pairs, kwargs); |
| return dict; |
| } |
| |
| @StarlarkMethod( |
| name = "enumerate", |
| doc = |
| "Returns a list of pairs (two-element tuples), with the index (int) and the item from" |
| + " the input sequence.\n<pre class=\"language-python\">" |
| + "enumerate([24, 21, 84]) == [(0, 24), (1, 21), (2, 84)]</pre>\n", |
| parameters = { |
| // Note Python uses 'sequence' keyword instead of 'list'. We may want to change tihs |
| // some day. |
| @Param(name = "list", doc = "input sequence.", named = true), |
| @Param(name = "start", doc = "start index.", defaultValue = "0", named = true), |
| }, |
| useStarlarkThread = true) |
| public StarlarkList<?> enumerate(Object input, StarlarkInt startI, StarlarkThread thread) |
| throws EvalException { |
| int start = Starlark.toInt(startI, "start"); |
| Object[] array = Starlark.toArray(input); |
| for (int i = 0; i < array.length; i++) { |
| array[i] = Tuple.pair(StarlarkInt.of(i + start), array[i]); // update in place |
| } |
| return StarlarkList.wrap(thread.mutability(), array); |
| } |
| |
| @StarlarkMethod( |
| name = "hash", |
| doc = |
| "Return a hash value for a string. This is computed deterministically using the same " |
| + "algorithm as Java's <code>String.hashCode()</code>, namely: " |
| + "<pre class=\"language-python\">s[0] * (31^(n-1)) + s[1] * (31^(n-2)) + ... + " |
| + "s[n-1]</pre> Hashing of values besides strings is not currently supported.", |
| // Deterministic hashing is important for the consistency of builds, hence why we |
| // promise a specific algorithm. This is in contrast to Java (Object.hashCode()) and |
| // Python, which promise stable hashing only within a given execution of the program. |
| parameters = {@Param(name = "value", doc = "String value to hash.")}) |
| public int hash(String value) throws EvalException { |
| return value.hashCode(); |
| } |
| |
| @StarlarkMethod( |
| name = "range", |
| doc = |
| "Creates a list where items go from <code>start</code> to <code>stop</code>, using a " |
| + "<code>step</code> increment. If a single argument is provided, items will " |
| + "range from 0 to that element." |
| + "<pre class=\"language-python\">range(4) == [0, 1, 2, 3]\n" |
| + "range(3, 9, 2) == [3, 5, 7]\n" |
| + "range(3, 0, -1) == [3, 2, 1]</pre>", |
| parameters = { |
| @Param( |
| name = "start_or_stop", |
| doc = |
| "Value of the start element if stop is provided, " |
| + "otherwise value of stop and the actual start is 0"), |
| @Param( |
| name = "stop_or_none", |
| allowedTypes = { |
| @ParamType(type = StarlarkInt.class), |
| @ParamType(type = NoneType.class), |
| }, |
| defaultValue = "None", |
| doc = |
| "optional index of the first item <i>not</i> to be included in the resulting " |
| + "list; generation of the list stops before <code>stop</code> is reached."), |
| @Param( |
| name = "step", |
| defaultValue = "1", |
| doc = "The increment (default is 1). It may be negative.") |
| }, |
| useStarlarkThread = true) |
| public Sequence<StarlarkInt> range( |
| StarlarkInt startOrStop, Object stopOrNone, StarlarkInt stepI, StarlarkThread thread) |
| throws EvalException { |
| int start; |
| int stop; |
| if (stopOrNone == Starlark.NONE) { |
| start = 0; |
| stop = startOrStop.toInt("stop"); |
| } else { |
| start = startOrStop.toInt("start"); |
| stop = Starlark.toInt(stopOrNone, "stop"); |
| } |
| int step = stepI.toInt("step"); |
| if (step == 0) { |
| throw Starlark.errorf("step cannot be 0"); |
| } |
| // TODO(adonovan): support arbitrary integers. |
| return new RangeList(start, stop, step); |
| } |
| |
| /** Returns true if the object has a field of the given name, otherwise false. */ |
| @StarlarkMethod( |
| name = "hasattr", |
| doc = |
| "Returns True if the object <code>x</code> has an attribute or method of the given " |
| + "<code>name</code>, otherwise False. Example:<br>" |
| + "<pre class=\"language-python\">hasattr(ctx.attr, \"myattr\")</pre>", |
| parameters = { |
| @Param(name = "x", doc = "The object to check."), |
| @Param(name = "name", doc = "The name of the attribute.") |
| }, |
| useStarlarkThread = true) |
| public boolean hasattr(Object obj, String name, StarlarkThread thread) throws EvalException { |
| return Starlark.hasattr(thread.getSemantics(), obj, name); |
| } |
| |
| @StarlarkMethod( |
| name = "getattr", |
| doc = |
| "Returns the struct's field of the given name if it exists. If not, it either returns " |
| + "<code>default</code> (if specified) or raises an error. " |
| + "<code>getattr(x, \"foobar\")</code> is equivalent to <code>x.foobar</code>." |
| + "<pre class=\"language-python\">getattr(ctx.attr, \"myattr\")\n" |
| + "getattr(ctx.attr, \"myattr\", \"mydefault\")</pre>", |
| parameters = { |
| @Param(name = "x", doc = "The struct whose attribute is accessed."), |
| @Param(name = "name", doc = "The name of the struct attribute."), |
| @Param( |
| name = "default", |
| defaultValue = "unbound", |
| doc = |
| "The default value to return in case the struct " |
| + "doesn't have an attribute of the given name.") |
| }, |
| useStarlarkThread = true) |
| public Object getattr(Object obj, String name, Object defaultValue, StarlarkThread thread) |
| throws EvalException, InterruptedException { |
| return Starlark.getattr( |
| thread.mutability(), |
| thread.getSemantics(), |
| obj, |
| name, |
| defaultValue == Starlark.UNBOUND ? null : defaultValue); |
| } |
| |
| @StarlarkMethod( |
| name = "dir", |
| doc = |
| "Returns a list of strings: the names of the attributes and " |
| + "methods of the parameter object.", |
| parameters = {@Param(name = "x", doc = "The object to check.")}, |
| useStarlarkThread = true) |
| public StarlarkList<?> dir(Object object, StarlarkThread thread) throws EvalException { |
| return Starlark.dir(thread.mutability(), thread.getSemantics(), object); |
| } |
| |
| @StarlarkMethod( |
| name = "fail", |
| doc = "Causes execution to fail with an error.", |
| parameters = { |
| // TODO(adonovan): remove. See https://github.com/bazelbuild/starlark/issues/47. |
| @Param( |
| name = "msg", |
| doc = |
| "Deprecated: use positional arguments instead. " |
| + "This argument acts like an implicit leading positional argument.", |
| defaultValue = "None", |
| positional = false, |
| named = true), |
| // TODO(adonovan): remove. See https://github.com/bazelbuild/starlark/issues/47. |
| @Param( |
| name = "attr", |
| allowedTypes = { |
| @ParamType(type = String.class), |
| @ParamType(type = NoneType.class), |
| }, |
| defaultValue = "None", |
| doc = |
| "Deprecated. Causes an optional prefix containing this string to be added to the" |
| + " error message.", |
| positional = false, |
| named = true), |
| @Param( |
| name = "sep", |
| defaultValue = "\" \"", |
| named = true, |
| positional = false, |
| doc = "The separator string between the objects, default is space (\" \").") |
| }, |
| extraPositionals = |
| @Param( |
| name = "args", |
| doc = |
| "A list of values, formatted with debugPrint (which is equivalent to str by" |
| + " default) and joined with sep (defaults to \" \"), that appear in the" |
| + " error message."), |
| useStarlarkThread = true) |
| public void fail(Object msg, Object attr, String sep, Tuple args, StarlarkThread thread) |
| throws EvalException { |
| Printer printer = new Printer(); |
| boolean needSeparator = false; |
| if (attr != Starlark.NONE) { |
| printer.append("attribute ").append((String) attr).append(":"); |
| needSeparator = true; |
| } |
| // msg acts like a leading element of args. |
| if (msg != Starlark.NONE) { |
| if (needSeparator) { |
| printer.append(sep); |
| } |
| printer.debugPrint(msg, thread); |
| needSeparator = true; |
| } |
| for (Object arg : args) { |
| if (needSeparator) { |
| printer.append(sep); |
| } |
| printer.debugPrint(arg, thread); |
| needSeparator = true; |
| } |
| throw Starlark.errorf("%s", printer.toString()); |
| } |
| |
| @StarlarkMethod( |
| name = "print", |
| doc = |
| "Prints <code>args</code> as debug output. It will be prefixed with the string <code>" |
| + "\"DEBUG\"</code> and the location (file and line number) of this call. The " |
| + "exact way in which the arguments are converted to strings is unspecified and may " |
| + "change at any time. In particular, it may be different from (and more detailed " |
| + "than) the formatting done by <a href='#str'><code>str()</code></a> and <a " |
| + "href='#repr'><code>repr()</code></a>." |
| + "<p>Using <code>print</code> in production code is discouraged due to the spam it " |
| + "creates for users. For deprecations, prefer a hard error using <a href=\"#fail\">" |
| + "<code>fail()</code></a> whenever possible.", |
| parameters = { |
| @Param( |
| name = "sep", |
| defaultValue = "\" \"", |
| named = true, |
| positional = false, |
| doc = "The separator string between the objects, default is space (\" \").") |
| }, |
| // NB: as compared to Python3, we're missing optional named-only arguments 'end' and 'file' |
| extraPositionals = @Param(name = "args", doc = "The objects to print."), |
| useStarlarkThread = true) |
| public void print(String sep, Sequence<?> args, StarlarkThread thread) throws EvalException { |
| Printer p = new Printer(); |
| String separator = ""; |
| for (Object x : args) { |
| p.append(separator); |
| p.debugPrint(x, thread); |
| separator = sep; |
| } |
| // The PRINT_TEST_MARKER key is used in tests to verify the effects of command-line options. |
| // See starlark_flag_test.sh, which runs bazel with --internal_starlark_flag_test_canary. |
| if (thread.getSemantics().getBool(StarlarkSemantics.PRINT_TEST_MARKER)) { |
| p.append("<== Starlark flag test ==>"); |
| } |
| |
| thread.getPrintHandler().print(thread, p.toString()); |
| } |
| |
| @StarlarkMethod( |
| name = "type", |
| doc = |
| "Returns the type name of its argument. This is useful for debugging and " |
| + "type-checking. Examples:" |
| + "<pre class=\"language-python\">" |
| + "type(2) == \"int\"\n" |
| + "type([1]) == \"list\"\n" |
| + "type(struct(a = 2)) == \"struct\"" |
| + "</pre>" |
| + "This function might change in the future. To write Python-compatible code and " |
| + "be future-proof, use it only to compare return values: " |
| + "<pre class=\"language-python\">" |
| + "if type(x) == type([]): # if x is a list" |
| + "</pre>", |
| parameters = {@Param(name = "x", doc = "The object to check type of.")}) |
| public String type(Object object) { |
| // There is no 'type' type in Starlark, so we return a string with the type name. |
| return Starlark.type(object); |
| } |
| |
| @StarlarkMethod( |
| name = "zip", |
| doc = |
| "Returns a <code>list</code> of <code>tuple</code>s, where the i-th tuple contains " |
| + "the i-th element from each of the argument sequences or iterables. The list has " |
| + "the size of the shortest input. With a single iterable argument, it returns a " |
| + "list of 1-tuples. With no arguments, it returns an empty list. Examples:" |
| + "<pre class=\"language-python\">" |
| + "zip() # == []\n" |
| + "zip([1, 2]) # == [(1,), (2,)]\n" |
| + "zip([1, 2], [3, 4]) # == [(1, 3), (2, 4)]\n" |
| + "zip([1, 2], [3, 4, 5]) # == [(1, 3), (2, 4)]</pre>", |
| extraPositionals = @Param(name = "args", doc = "lists to zip."), |
| useStarlarkThread = true) |
| public StarlarkList<?> zip(Sequence<?> args, StarlarkThread thread) throws EvalException { |
| StarlarkList<Tuple> result = StarlarkList.newList(thread.mutability()); |
| int ncols = args.size(); |
| if (ncols > 0) { |
| Iterator<?>[] iterators = new Iterator<?>[ncols]; |
| for (int i = 0; i < ncols; i++) { |
| iterators[i] = Starlark.toIterable(args.get(i)).iterator(); |
| } |
| rows: |
| for (; ; ) { |
| Object[] elem = new Object[ncols]; |
| for (int i = 0; i < ncols; i++) { |
| Iterator<?> it = iterators[i]; |
| if (!it.hasNext()) { |
| break rows; |
| } |
| elem[i] = it.next(); |
| } |
| result.addElement(Tuple.wrap(elem)); |
| } |
| } |
| return result; |
| } |
| |
| /** Starlark bool type. */ |
| @StarlarkBuiltin( |
| name = "bool", |
| category = "core", |
| doc = |
| "A type to represent booleans. There are only two possible values: " |
| + "True and False. Any value can be converted to a boolean using the " |
| + "<a href=\"../globals/all.html#bool\">bool</a> function.") |
| static final class BoolModule implements StarlarkValue {} // (documentation only) |
| } |