blob: ebb91e0e1c6dc30c8935c60cd85261be2b57aea8 [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 com.google.common.base.Ascii;
import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
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 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\">min(2, 5, 4) == 2\n"
+ "min([5, 6, 3]) == 3</pre>",
extraPositionals = @Param(name = "args", doc = "The elements to be checked."))
public Object min(Sequence<?> args) throws EvalException {
return findExtreme(args, Starlark.ORDERING.reverse());
}
@StarlarkMethod(
name = "max",
doc =
"Returns the largest one of all given arguments. "
+ "If only one 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\">max(2, 5, 4) == 5\n"
+ "max([5, 6, 3]) == 6</pre>",
extraPositionals = @Param(name = "args", doc = "The elements to be checked."))
public Object max(Sequence<?> args) throws EvalException {
return findExtreme(args, Starlark.ORDERING);
}
/** Returns the maximum element from this list, as determined by maxOrdering. */
private static Object findExtreme(Sequence<?> args, Ordering<Object> maxOrdering)
throws EvalException {
// 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 {
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);
}
}
@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\">sorted([3, 5, 4]) == [3, 4, 5]</pre>",
parameters = {
@Param(name = "iterable", doc = "The iterable sequence to sort."),
@Param(
name = "key",
doc = "An optional function applied to each element before comparison.",
named = true,
defaultValue = "None",
positional = false),
@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.
if (!(key instanceof StarlarkCallable)) {
throw Starlark.errorf("for key, got %s, want callable", Starlark.type(key));
}
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 list that contains the elements of the original sequence in reversed order."
+ "<pre class=\"language-python\">reversed([3, 5, 4]) == [4, 5, 3]</pre>",
parameters = {
@Param(name = "sequence", doc = "The sequence (list or tuple) to be reversed."),
},
useStarlarkThread = true)
public StarlarkList<?> reversed(Sequence<?> 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 Integer 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.")})
public String str(Object x) throws EvalException {
return Starlark.str(x);
}
@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>&lt;sign&gt;&lt;prefix&gt;&lt;digits&gt;</code>. "
+ " <code>&lt;sign&gt;</code> is either <code>\"+\"</code>, <code>\"-\"</code>, "
+ " or empty (interpreted as positive). <code>&lt;digits&gt;</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>&lt;prefix&gt;</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=\"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 = "args",
defaultValue = "[]",
doc =
"Either a dictionary or a list of entries. Entries must be tuples or lists with "
+ "exactly two elements: key, value."),
},
extraKeywords = @Param(name = "kwargs", doc = "Dictionary of additional entries."),
useStarlarkThread = true)
public Dict<?, ?> dict(Object args, Dict<String, Object> kwargs, StarlarkThread thread)
throws EvalException {
Dict<?, ?> dict =
args instanceof Dict
? (Dict) args
: Dict.getDictFromArgs("dict", args, thread.mutability());
return Dict.plus(dict, kwargs, thread.mutability());
}
@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 Integer 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 =
"Raises an error that cannot be intercepted. It can be used anywhere, "
+ "both in the loading phase and in the analysis phase.",
parameters = {
@Param(
name = "msg",
doc = "Error to display for the user. The object is converted to a string.",
defaultValue = "None",
named = true),
@Param(
name = "attr",
allowedTypes = {
@ParamType(type = String.class),
@ParamType(type = NoneType.class),
},
defaultValue = "None",
doc =
"The name of the attribute that caused the error. This is used only for "
+ "error reporting.",
named = true)
})
public NoneType fail(Object msg, Object attr) throws EvalException {
String str = Starlark.str(msg);
if (attr != Starlark.NONE) {
str = Starlark.format("attribute %s: %s", attr, str);
}
throw Starlark.errorf("%s", str);
}
@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 NoneType 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);
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());
return Starlark.NONE;
}
@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 {
Iterator<?>[] iterators = new Iterator<?>[args.size()];
for (int i = 0; i < args.size(); i++) {
iterators[i] = Starlark.toIterable(args.get(i)).iterator();
}
ArrayList<Tuple<?>> result = new ArrayList<>();
boolean allHasNext;
do {
allHasNext = !args.isEmpty();
List<Object> elem = Lists.newArrayListWithExpectedSize(args.size());
for (Iterator<?> iterator : iterators) {
if (iterator.hasNext()) {
elem.add(iterator.next());
} else {
allHasNext = false;
}
}
if (allHasNext) {
result.add(Tuple.copyOf(elem));
}
} while (allHasNext);
return StarlarkList.copyOf(thread.mutability(), result);
}
/** Starlark bool type. */
@StarlarkBuiltin(
name = "bool",
category = "core",
doc =
"A type to represent booleans. There are only two possible values: "
+ "<a href=\"globals.html#True\">True</a> and "
+ "<a href=\"globals.html#False\">False</a>. "
+ "Any value can be converted to a boolean using the "
+ "<a href=\"globals.html#bool\">bool</a> function.")
static final class BoolModule implements StarlarkValue {} // (documentation only)
}