// 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 com.google.devtools.build.lib.syntax; import static java.util.stream.Collectors.joining; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.collect.Ordering; import com.google.devtools.build.lib.collect.nestedset.NestedSet; import com.google.devtools.build.lib.collect.nestedset.Order; import com.google.devtools.build.lib.events.Event; import com.google.devtools.build.lib.events.Location; import com.google.devtools.build.lib.skylarkinterface.Param; import com.google.devtools.build.lib.skylarkinterface.SkylarkModule; import com.google.devtools.build.lib.skylarkinterface.SkylarkModuleCategory; import com.google.devtools.build.lib.skylarkinterface.SkylarkSignature; import com.google.devtools.build.lib.syntax.EvalUtils.ComparisonException; import com.google.devtools.build.lib.syntax.SkylarkList.MutableList; import com.google.devtools.build.lib.syntax.SkylarkList.Tuple; import com.google.devtools.build.lib.syntax.Type.ConversionException; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import java.util.Set; import java.util.TreeSet; import javax.annotation.Nullable; /** A helper class containing built in functions for the Skylark language. */ public class MethodLibrary { private MethodLibrary() {} @SkylarkSignature( name = "min", returnType = Object.class, 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). " + "
min(2, 5, 4) == 2\n"
            + "min([5, 6, 3]) == 3
", extraPositionals = @Param(name = "args", type = SkylarkList.class, doc = "The elements to be checked."), useLocation = true, useEnvironment = true ) private static final BuiltinFunction min = new BuiltinFunction("min") { @SuppressWarnings("unused") // Accessed via Reflection. public Object invoke(SkylarkList args, Location loc, Environment env) throws EvalException { try { return findExtreme(args, EvalUtils.SKYLARK_COMPARATOR.reverse(), loc, env); } catch (ComparisonException e) { throw new EvalException(loc, e); } } }; @SkylarkSignature( name = "max", returnType = Object.class, 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). " + "
max(2, 5, 4) == 5\n"
            + "max([5, 6, 3]) == 6
", extraPositionals = @Param(name = "args", type = SkylarkList.class, doc = "The elements to be checked."), useLocation = true, useEnvironment = true ) private static final BuiltinFunction max = new BuiltinFunction("max") { @SuppressWarnings("unused") // Accessed via Reflection. public Object invoke(SkylarkList args, Location loc, Environment env) throws EvalException { try { return findExtreme(args, EvalUtils.SKYLARK_COMPARATOR, loc, env); } catch (ComparisonException e) { throw new EvalException(loc, e); } } }; /** Returns the maximum element from this list, as determined by maxOrdering. */ private static Object findExtreme( SkylarkList args, Ordering maxOrdering, Location loc, Environment env) 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. try { Iterable items = (args.size() == 1) ? EvalUtils.toIterable(args.get(0), loc, env) : args; return maxOrdering.max(items); } catch (NoSuchElementException ex) { throw new EvalException(loc, "expected at least one item"); } } @SkylarkSignature( name = "all", returnType = Boolean.class, doc = "Returns true if all elements evaluate to True or if the collection is empty. " + "Elements are converted to boolean using the bool function." + "
all([\"hello\", 3, True]) == True\n"
            + "all([-1, 0, 1]) == False
", parameters = { @Param(name = "elements", type = Object.class, doc = "A string or a collection of elements.") }, useLocation = true, useEnvironment = true ) private static final BuiltinFunction all = new BuiltinFunction("all") { @SuppressWarnings("unused") // Accessed via Reflection. public Boolean invoke(Object collection, Location loc, Environment env) throws EvalException { return !hasElementWithBooleanValue(collection, false, loc, env); } }; @SkylarkSignature( name = "any", returnType = Boolean.class, doc = "Returns true if at least one element evaluates to True. " + "Elements are converted to boolean using the bool function." + "
any([-1, 0, 1]) == True\n"
            + "any([False, 0, \"\"]) == False
", parameters = { @Param(name = "elements", type = Object.class, doc = "A string or a collection of elements.") }, useLocation = true, useEnvironment = true ) private static final BuiltinFunction any = new BuiltinFunction("any") { @SuppressWarnings("unused") // Accessed via Reflection. public Boolean invoke(Object collection, Location loc, Environment env) throws EvalException { return hasElementWithBooleanValue(collection, true, loc, env); } }; private static boolean hasElementWithBooleanValue( Object collection, boolean value, Location loc, Environment env) throws EvalException { Iterable iterable = EvalUtils.toIterable(collection, loc, env); for (Object obj : iterable) { if (EvalUtils.toBoolean(obj) == value) { return true; } } return false; } // supported list methods @SkylarkSignature( name = "sorted", returnType = MutableList.class, doc = "Sort a collection. Elements should all belong to the same orderable type, they are sorted " + "by their value (in ascending order). " + "It is an error if elements are not comparable (for example int with string)." + "
sorted([3, 5, 4]) == [3, 4, 5]
", parameters = {@Param(name = "self", type = Object.class, doc = "This collection.")}, useLocation = true, useEnvironment = true ) private static final BuiltinFunction sorted = new BuiltinFunction("sorted") { public MutableList invoke(Object self, Location loc, Environment env) throws EvalException { try { return MutableList.copyOf( env, EvalUtils.SKYLARK_COMPARATOR.sortedCopy(EvalUtils.toCollection(self, loc, env))); } catch (EvalUtils.ComparisonException e) { throw new EvalException(loc, e); } } }; @SkylarkSignature( name = "reversed", returnType = MutableList.class, doc = "Returns a list that contains the elements of the original sequence in reversed order." + "
reversed([3, 5, 4]) == [4, 5, 3]
", parameters = { @Param( name = "sequence", type = Object.class, doc = "The sequence to be reversed (string, list or tuple)." ) }, useLocation = true, useEnvironment = true ) private static final BuiltinFunction reversed = new BuiltinFunction("reversed") { @SuppressWarnings("unused") // Accessed via Reflection. public MutableList invoke(Object sequence, Location loc, Environment env) throws EvalException { // We only allow lists and strings. if (sequence instanceof SkylarkDict) { throw new EvalException( loc, "Argument to reversed() must be a sequence, not a dictionary."); } else if (sequence instanceof NestedSet || sequence instanceof SkylarkNestedSet) { throw new EvalException( loc, "Argument to reversed() must be a sequence, not a depset."); } ArrayDeque tmpList = new ArrayDeque<>(); for (Object element : EvalUtils.toIterable(sequence, loc, env)) { tmpList.addFirst(element); } return MutableList.copyOf(env, tmpList); } }; @SkylarkSignature( name = "tuple", returnType = Tuple.class, doc = "Converts a collection (e.g. list, tuple or dictionary) to a tuple." + "
tuple([1, 2]) == (1, 2)\n"
            + "tuple((2, 3, 2)) == (2, 3, 2)\n"
            + "tuple({5: \"a\", 2: \"b\", 4: \"c\"}) == (5, 2, 4)
", parameters = {@Param(name = "x", doc = "The object to convert.")}, useLocation = true, useEnvironment = true ) private static final BuiltinFunction tuple = new BuiltinFunction("tuple") { public Tuple invoke(Object x, Location loc, Environment env) throws EvalException { return Tuple.copyOf(EvalUtils.toCollection(x, loc, env)); } }; @SkylarkSignature( name = "list", returnType = MutableList.class, doc = "Converts a collection (e.g. list, tuple or dictionary) to a list." + "
list([1, 2]) == [1, 2]\n"
            + "list((2, 3, 2)) == [2, 3, 2]\n"
            + "list({5: \"a\", 2: \"b\", 4: \"c\"}) == [5, 2, 4]
", parameters = {@Param(name = "x", doc = "The object to convert.")}, useLocation = true, useEnvironment = true ) private static final BuiltinFunction list = new BuiltinFunction("list") { public MutableList invoke(Object x, Location loc, Environment env) throws EvalException { return MutableList.copyOf(env, EvalUtils.toCollection(x, loc, env)); } }; @SkylarkSignature( name = "len", returnType = Integer.class, doc = "Returns the length of a string, list, tuple, depset, or dictionary.", parameters = {@Param(name = "x", doc = "The object to check length of.")}, useLocation = true, useEnvironment = true ) private static final BuiltinFunction len = new BuiltinFunction("len") { public Integer invoke(Object x, Location loc, Environment env) throws EvalException { if (env.getSemantics().incompatibleDepsetIsNotIterable() && x instanceof SkylarkNestedSet) { throw new EvalException( loc, EvalUtils.getDataTypeName(x) + " is not iterable. You may use `len(.to_list())` instead. Use " + "--incompatible_depset_is_not_iterable=false to temporarily disable this " + "check."); } int l = EvalUtils.size(x); if (l == -1) { throw new EvalException(loc, EvalUtils.getDataTypeName(x) + " is not iterable"); } return l; } }; @SkylarkSignature( name = "str", returnType = String.class, doc = "Converts any object to string. This is useful for debugging." + "
str(\"ab\") == \"ab\"\n"
            + "str(8) == \"8\"
", parameters = {@Param(name = "x", doc = "The object to convert.")} ) private static final BuiltinFunction str = new BuiltinFunction("str") { public String invoke(Object x) { return Printer.str(x); } }; @SkylarkSignature( name = "repr", returnType = String.class, doc = "Converts any object to a string representation. This is useful for debugging.
" + "
repr(\"ab\") == '\"ab\"'
", parameters = {@Param(name = "x", doc = "The object to convert.")} ) private static final BuiltinFunction repr = new BuiltinFunction("repr") { public String invoke(Object x) { return Printer.repr(x); } }; @SkylarkSignature( name = "bool", returnType = Boolean.class, doc = "Constructor for the bool type. " + "It returns False if the object is None, False" + ", an empty string (\"\"), the number 0, or an " + "empty collection (e.g. (), []). " + "Otherwise, it returns True.", parameters = {@Param(name = "x", doc = "The variable to convert.")} ) private static final BuiltinFunction bool = new BuiltinFunction("bool") { public Boolean invoke(Object x) throws EvalException { return EvalUtils.toBoolean(x); } }; @SkylarkSignature( name = "int", returnType = Integer.class, doc = "Returns x as an int value." + "
    " + "
  • If x is already an int, it is returned as-is." + "
  • If x is a boolean, a true value returns 1 and a false value " + " returns 0." + "
  • If x is a string, it must have the format " + " <sign><prefix><digits>. " + " <sign> is either \"+\", \"-\", or " + " empty (interpreted as positive). <digits> are a sequence of " + " digits from 0 up to base - 1, where the letters a-z (or " + " equivalently, A-Z) are used as digits for 10-35. In the case where " + " base is 2/8/16, <prefix> is optional and may be " + " 0b/0o/0x (or equivalently, 0B/0O/0X) respectively; if the base is " + " any other value besides these bases or the special value 0, the prefix must be " + " empty. In the case where base 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 base 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." + "
" + "This function fails if x is any other type, or if the value is a " + "string not satisfying the above format. Unlike Python's int() " + "function, this function does not allow zero arguments, and does not allow " + "extraneous whitespace for string arguments." + "

Examples:" + "

"
            + "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"
            + "
", parameters = { @Param(name = "x", type = Object.class, doc = "The string to convert."), @Param( name = "base", type = Object.class, 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 x were an integer " + "literal. This parameter must not be supplied if the value is not a string." ) }, useLocation = true ) private static final BuiltinFunction int_ = new BuiltinFunction("int") { private final ImmutableMap intPrefixes = ImmutableMap.of("0b", 2, "0o", 8, "0x", 16); @SuppressWarnings("unused") public Integer invoke(Object x, Object base, Location loc) throws EvalException { if (x instanceof String) { if (base == Runtime.UNBOUND) { base = 10; } else if (!(base instanceof Integer)) { throw new EvalException( loc, "base must be an integer (got '" + EvalUtils.getDataTypeName(base) + "')"); } return fromString((String) x, loc, (Integer) base); } else { if (base != Runtime.UNBOUND) { throw new EvalException(loc, "int() can't convert non-string with explicit base"); } if (x instanceof Boolean) { return ((Boolean) x).booleanValue() ? 1 : 0; } else if (x instanceof Integer) { return (Integer) x; } throw new EvalException( loc, Printer.format("%r is not of type string or int or bool", x)); } } private int fromString(String string, Location loc, int base) throws EvalException { String stringForErrors = string; boolean isNegative = false; if (string.isEmpty()) { throw new EvalException( loc, Printer.format("string argument to int() cannot be empty")); } char c = string.charAt(0); if (c == '+') { string = string.substring(1); } else if (c == '-') { string = string.substring(1); isNegative = true; } String prefix = getIntegerPrefix(string); String digits; if (prefix == null) { // Nothing to strip. Infer base 10 if autodetection was requested (base == 0). digits = string; if (base == 0) { if (string.length() > 1 && string.startsWith("0")) { // We don't infer the base when input starts with '0' (due // to confusion between octal and decimal). throw new EvalException( loc, Printer.format( "cannot infer base for int() when value begins with a 0: %r", stringForErrors)); } base = 10; } } else { // Strip prefix. Infer base from prefix if unknown (base == 0), or else verify its // consistency. digits = string.substring(prefix.length()); int expectedBase = intPrefixes.get(prefix); if (base == 0) { base = expectedBase; } else if (base != expectedBase) { throw new EvalException( loc, Printer.format( "invalid literal for int() with base %d: %r", base, stringForErrors)); } } if (base < 2 || base > 36) { throw new EvalException(loc, "int() base must be >= 2 and <= 36"); } try { // Negate by prepending a negative symbol, rather than by using arithmetic on the // result, to handle the edge case of -2^31 correctly. String parseable = isNegative ? "-" + digits : digits; return Integer.parseInt(parseable, base); } catch (NumberFormatException | ArithmeticException e) { throw new EvalException( loc, Printer.format( "invalid literal for int() with base %d: %r", base, stringForErrors)); } } @Nullable private String getIntegerPrefix(String value) { value = value.toLowerCase(); for (String prefix : intPrefixes.keySet()) { if (value.startsWith(prefix)) { return prefix; } } return null; } }; @SkylarkSignature( name = "dict", returnType = SkylarkDict.class, doc = "Creates a dictionary 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", type = Object.class, 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."), useLocation = true, useEnvironment = true ) private static final BuiltinFunction dict = new BuiltinFunction("dict") { public SkylarkDict invoke( Object args, SkylarkDict kwargs, Location loc, Environment env) throws EvalException { SkylarkDict argsDict = (args instanceof SkylarkDict) ? (SkylarkDict) args : getDictFromArgs(args, loc, env); return SkylarkDict.plus(argsDict, kwargs, env); } private SkylarkDict getDictFromArgs( Object args, Location loc, Environment env) throws EvalException { SkylarkDict result = SkylarkDict.of(env); int pos = 0; for (Object element : Type.OBJECT_LIST.convert(args, "parameter args in dict()")) { List pair = convertToPair(element, pos, loc); result.put(pair.get(0), pair.get(1), loc, env); ++pos; } return result; } private List convertToPair(Object element, int pos, Location loc) throws EvalException { try { List tuple = Type.OBJECT_LIST.convert(element, ""); int numElements = tuple.size(); if (numElements != 2) { throw new EvalException( location, String.format( "item #%d has length %d, but exactly two elements are required", pos, numElements)); } return tuple; } catch (ConversionException e) { throw new EvalException( loc, String.format("cannot convert item #%d to a sequence", pos)); } } }; @SkylarkSignature( name = "enumerate", returnType = MutableList.class, doc = "Returns a list of pairs (two-element tuples), with the index (int) and the item from" + " the input list.\n
"
            + "enumerate([24, 21, 84]) == [(0, 24), (1, 21), (2, 84)]
\n", parameters = {@Param(name = "list", type = SkylarkList.class, doc = "input list.")}, useEnvironment = true ) private static final BuiltinFunction enumerate = new BuiltinFunction("enumerate") { public MutableList invoke(SkylarkList input, Environment env) throws EvalException { int count = 0; ArrayList> result = new ArrayList<>(input.size()); for (Object obj : input) { result.add(Tuple.of(count, obj)); count++; } return MutableList.wrapUnsafe(env, result); } }; @SkylarkSignature( name = "hash", returnType = Integer.class, doc = "Return a hash value for a string. This is computed deterministically using the same " + "algorithm as Java's String.hashCode(), namely: " + "
s[0] * (31^(n-1)) + s[1] * (31^(n-2)) + ... + s[n-1]"
            + "
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", type = String.class, doc = "String value to hash.")} ) private static final BuiltinFunction hash = new BuiltinFunction("hash") { public Integer invoke(String value) throws EvalException { return value.hashCode(); } }; @SkylarkSignature( name = "range", returnType = SkylarkList.class, doc = "Creates a list where items go from start to stop, using a " + "step increment. If a single argument is provided, items will " + "range from 0 to that element." + "
range(4) == [0, 1, 2, 3]\n"
              + "range(3, 9, 2) == [3, 5, 7]\n"
              + "range(3, 0, -1) == [3, 2, 1]
", parameters = { @Param( name = "start_or_stop", type = Integer.class, 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", type = Integer.class, noneable = true, defaultValue = "None", doc = "optional index of the first item not to be included in the resulting " + "list; generation of the list stops before stop is reached."), @Param( name = "step", type = Integer.class, defaultValue = "1", doc = "The increment (default is 1). It may be negative.") }, useLocation = true, useEnvironment = true) private static final BuiltinFunction range = new BuiltinFunction("range") { public SkylarkList invoke( Integer startOrStop, Object stopOrNone, Integer step, Location loc, Environment env) throws EvalException { int start; int stop; if (stopOrNone == Runtime.NONE) { start = 0; stop = startOrStop; } else { start = startOrStop; stop = Type.INTEGER.convert(stopOrNone, "'stop' operand of 'range'"); } if (step == 0) { throw new EvalException(loc, "step cannot be 0"); } RangeList range = RangeList.of(start, stop, step); return env.getSemantics().incompatibleRangeType() ? range : range.toMutableList(env); } }; /** Returns true if the object has a field of the given name, otherwise false. */ @SkylarkSignature( name = "hasattr", returnType = Boolean.class, doc = "Returns True if the object x has an attribute or method of the given " + "name, otherwise False. Example:
" + "
hasattr(ctx.attr, \"myattr\")
", parameters = { @Param(name = "x", doc = "The object to check."), @Param(name = "name", type = String.class, doc = "The name of the attribute.") }, useEnvironment = true ) private static final BuiltinFunction hasattr = new BuiltinFunction("hasattr") { @SuppressWarnings("unused") public Boolean invoke(Object obj, String name, Environment env) throws EvalException { if (obj instanceof ClassObject && ((ClassObject) obj).getValue(name) != null) { return true; } // shouldn't this filter things with struct_field = false? return DotExpression.hasMethod(obj, name); } }; @SkylarkSignature( name = "getattr", doc = "Returns the struct's field of the given name if it exists. If not, it either returns " + "default (if specified) or raises an error. Built-in methods cannot " + "currently be retrieved in this way; doing so will result in an error if a " + "default is not given. getattr(x, \"foobar\") is " + "equivalent to x.foobar." + "
getattr(ctx.attr, \"myattr\")\n"
            + "getattr(ctx.attr, \"myattr\", \"mydefault\")
", 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." ) }, useLocation = true, useEnvironment = true ) private static final BuiltinFunction getattr = new BuiltinFunction("getattr") { @SuppressWarnings("unused") public Object invoke( Object obj, String name, Object defaultValue, Location loc, Environment env) throws EvalException, InterruptedException { Object result = DotExpression.eval(obj, name, loc, env); if (result == null) { if (defaultValue != Runtime.UNBOUND) { return defaultValue; } throw DotExpression.getMissingFieldException(obj, name, loc, "attribute"); } return result; } }; @SkylarkSignature( name = "dir", returnType = MutableList.class, 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.")}, useLocation = true, useEnvironment = true ) private static final BuiltinFunction dir = new BuiltinFunction("dir") { public MutableList invoke(Object object, Location loc, Environment env) throws EvalException { // Order the fields alphabetically. Set fields = new TreeSet<>(); if (object instanceof ClassObject) { fields.addAll(((ClassObject) object).getFieldNames()); } fields.addAll(Runtime.getBuiltinRegistry().getFunctionNames(object.getClass())); fields.addAll(FuncallExpression.getMethodNames(object.getClass())); return MutableList.copyOf(env, fields); } }; @SkylarkSignature( 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.", returnType = Runtime.NoneType.class, parameters = { @Param( name = "msg", type = Object.class, doc = "Error to display for the user. The object is converted to a string." ), @Param( name = "attr", type = String.class, noneable = true, defaultValue = "None", doc = "The name of the attribute that caused the error. This is used only for " + "error reporting." ) }, useLocation = true ) private static final BuiltinFunction fail = new BuiltinFunction("fail") { public Runtime.NoneType invoke(Object msg, Object attr, Location loc) throws EvalException { String str = Printer.str(msg); if (attr != Runtime.NONE) { str = String.format("attribute %s: %s", attr, str); } throw new EvalException(loc, str); } }; @SkylarkSignature( name = "print", returnType = Runtime.NoneType.class, doc = "Prints args as debug output. It will be prefixed with the string " + "\"DEBUG\" 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 str() and repr()." + "

Using print in production code is discouraged due to the spam it " + "creates for users. For deprecations, prefer a hard error using " + "fail() whenever possible.", parameters = { @Param( name = "sep", type = String.class, 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."), useLocation = true, useEnvironment = true ) private static final BuiltinFunction print = new BuiltinFunction("print") { public Runtime.NoneType invoke( String sep, SkylarkList starargs, Location loc, Environment env) throws EvalException { String msg = starargs.stream().map(Printer::debugPrint).collect(joining(sep)); // As part of the integration test "skylark_flag_test.sh", if the // "--internal_skylark_flag_test_canary" flag is enabled, append an extra marker string to // the output. if (env.getSemantics().internalSkylarkFlagTestCanary()) { msg += "<== skylark flag test ==>"; } env.handleEvent(Event.debug(loc, msg)); return Runtime.NONE; } }; @SkylarkSignature( name = "type", returnType = String.class, doc = "Returns the type name of its argument. This is useful for debugging and " + "type-checking. Examples:" + "

"
              + "type(2) == \"int\"\n"
              + "type([1]) == \"list\"\n"
              + "type(struct(a = 2)) == \"struct\""
              + "
" + "This function might change in the future. To write Python-compatible code and " + "be future-proof, use it only to compare return values: " + "
"
              + "if type(x) == type([]):  # if x is a list"
              + "
", parameters = {@Param(name = "x", doc = "The object to check type of.")}) private static final BuiltinFunction type = new BuiltinFunction("type") { public String invoke(Object object) { // There is no 'type' type in Skylark, so we return a string with the type name. return EvalUtils.getDataTypeName(object, false); } }; @SkylarkSignature( name = "depset", returnType = SkylarkNestedSet.class, doc = "Creates a depset. The direct parameter is a list " + "of direct elements of the depset, and transitive parameter is " + "a list of depsets whose elements become indirect elements of the created depset. " + "The order in which elements are returned when the depset is converted to a list " + "is specified by the order parameter. " + "See the Depsets overview for more information. " + "

All elements (direct and indirect) of a depset must be of the same type. " + "

The order of the created depset should be compatible with the order of " + "its transitive depsets. \"default\" order is compatible " + "with any other order, all other orders are only compatible with themselves." + "

Note on backward/forward compatibility. This function currently accepts a " + "positional items parameter. It is deprecated and will be removed " + "in the future, and after its removal direct will become a sole " + "positional parameter of the depset function. Thus, both of the " + "following calls are equivalent and future-proof:
" + "

"
            + "depset(['a', 'b'], transitive = [...])\n"
            + "depset(direct = ['a', 'b'], transitive = [...])\n"
            + "
", parameters = { @Param( name = "items", type = Object.class, defaultValue = "[]", doc = "Deprecated: Either an iterable whose items become the direct elements of " + "the new depset, in left-to-right order, or else a depset that becomes " + "a transitive element of the new depset. In the latter case, " + "transitive cannot be specified."), @Param( name = "order", type = String.class, defaultValue = "\"default\"", doc = "The traversal strategy for the new depset. See here for " + "the possible values."), @Param( name = "direct", type = SkylarkList.class, defaultValue = "None", positional = false, named = true, noneable = true, doc = "A list of direct elements of a depset."), @Param( name = "transitive", named = true, positional = false, type = SkylarkList.class, generic1 = SkylarkNestedSet.class, noneable = true, doc = "A list of depsets whose elements will become indirect elements of the depset.", defaultValue = "None") }, useLocation = true) private static final BuiltinFunction depset = new BuiltinFunction("depset") { public SkylarkNestedSet invoke( Object items, String orderString, Object direct, Object transitive, Location loc) throws EvalException { Order order; try { order = Order.parse(orderString); } catch (IllegalArgumentException ex) { throw new EvalException(loc, ex); } if (transitive == Runtime.NONE && direct == Runtime.NONE) { // Legacy behavior. return SkylarkNestedSet.of(order, items, loc); } if (direct != Runtime.NONE && !isEmptySkylarkList(items)) { throw new EvalException( loc, "Do not pass both 'direct' and 'items' argument to depset constructor."); } // Non-legacy behavior: either 'transitive' or 'direct' were specified. Iterable directElements; if (direct != Runtime.NONE) { directElements = ((SkylarkList) direct).getContents(Object.class, "direct"); } else { SkylarkType.checkType(items, SkylarkList.class, "items"); directElements = ((SkylarkList) items).getContents(Object.class, "items"); } Iterable transitiveList; if (transitive != Runtime.NONE) { SkylarkType.checkType(transitive, SkylarkList.class, "transitive"); transitiveList = ((SkylarkList) transitive).getContents(SkylarkNestedSet.class, "transitive"); } else { transitiveList = ImmutableList.of(); } SkylarkNestedSet.Builder builder = SkylarkNestedSet.builder(order, loc); for (Object directElement : directElements) { builder.addDirect(directElement); } for (SkylarkNestedSet transitiveSet : transitiveList) { builder.addTransitive(transitiveSet); } return builder.build(); } }; private static boolean isEmptySkylarkList(Object o) { return o instanceof SkylarkList && ((SkylarkList) o).isEmpty(); } /** * Returns a function-value implementing "select" (i.e. configurable attributes) in the specified * package context. */ @SkylarkSignature( name = "select", doc = "select() is the helper function that makes a rule attribute " + "configurable. " + "See build encyclopedia for details.", parameters = { @Param(name = "x", type = SkylarkDict.class, doc = "The parameter to convert."), @Param( name = "no_match_error", type = String.class, defaultValue = "''", doc = "Optional custom error to report if no condition matches.") }, useLocation = true) private static final BuiltinFunction select = new BuiltinFunction("select") { public Object invoke(SkylarkDict dict, String noMatchError, Location loc) throws EvalException { for (Object key : dict.keySet()) { if (!(key instanceof String)) { throw new EvalException( loc, String.format("Invalid key: %s. select keys must be label references", key)); } } return SelectorList.of(new SelectorValue(dict, noMatchError)); } }; @SkylarkSignature( name = "zip", doc = "Returns a list of tuples, 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:" + "
"
            + "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)]
", extraPositionals = @Param(name = "args", doc = "lists to zip."), returnType = MutableList.class, useLocation = true, useEnvironment = true ) private static final BuiltinFunction zip = new BuiltinFunction("zip") { public MutableList invoke(SkylarkList args, Location loc, Environment env) throws EvalException { Iterator[] iterators = new Iterator[args.size()]; for (int i = 0; i < args.size(); i++) { iterators[i] = EvalUtils.toIterable(args.get(i), loc, env).iterator(); } ArrayList> result = new ArrayList<>(); boolean allHasNext; do { allHasNext = !args.isEmpty(); List 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 MutableList.wrapUnsafe(env, result); } }; /** Skylark int type. */ @SkylarkModule( name = "int", category = SkylarkModuleCategory.BUILTIN, doc = "A type to represent integers. It can represent any number between -2147483648 and " + "2147483647 (included). " + "Examples of int values:
" + "
"
            + "153\n"
            + "0x2A  # hexadecimal literal\n"
            + "054  # octal literal\n"
            + "23 * 2 + 5\n"
            + "100 / -7\n"
            + "100 % -7  # -5 (unlike in some other languages)\n"
            + "int(\"18\")\n"
            + "
" ) public static final class IntModule {} /** Skylark bool type. */ @SkylarkModule( name = "bool", category = SkylarkModuleCategory.BUILTIN, 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 " + "bool function." ) public static final class BoolModule {} /** Adds bindings for all the builtin functions of this class to the given map builder. */ public static void addBindingsToBuilder(ImmutableMap.Builder builder) { for (BaseFunction function : allFunctions) { builder.put(function.getName(), function); } } private static final ImmutableList allFunctions = ImmutableList.of( all, any, bool, depset, dict, dir, fail, getattr, hasattr, hash, enumerate, int_, len, list, max, min, print, range, repr, reversed, select, sorted, str, tuple, type, zip); static { SkylarkSignatureProcessor.configureSkylarkFunctions(MethodLibrary.class); } }