// 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.base.CharMatcher; import com.google.common.base.Joiner; 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.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.ArrayList; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import java.util.TreeSet; import java.util.regex.Matcher; import java.util.regex.Pattern; /** A helper class containing built in functions for the Skylark language. */ public class MethodLibrary { private MethodLibrary() {} // Emulate Python substring function // It converts out of range indices, and never fails private static String pythonSubstring(String str, int start, Object end, String msg) throws ConversionException { if (start == 0 && EvalUtils.isNullOrNone(end)) { return str; } start = EvalUtils.clampRangeEndpoint(start, str.length()); int stop; if (EvalUtils.isNullOrNone(end)) { stop = str.length(); } else { stop = EvalUtils.clampRangeEndpoint(Type.INTEGER.convert(end, msg), str.length()); } if (start >= stop) { return ""; } return str.substring(start, stop); } // supported string methods @SkylarkSignature(name = "join", objectType = StringModule.class, returnType = String.class, doc = "Returns a string in which the string elements of the argument have been " + "joined by this string as a separator. Example:
" + "
\"|\".join([\"a\", \"b\", \"c\"]) == \"a|b|c\"
", parameters = { @Param(name = "self", type = String.class, doc = "This string, a separator."), @Param(name = "elements", type = SkylarkList.class, doc = "The objects to join.")}) private static final BuiltinFunction join = new BuiltinFunction("join") { public String invoke(String self, SkylarkList elements) throws ConversionException { return Joiner.on(self).join(elements); } }; @SkylarkSignature(name = "lower", objectType = StringModule.class, returnType = String.class, doc = "Returns the lower case version of this string.", parameters = { @Param(name = "self", type = String.class, doc = "This string, to convert to lower case.")}) private static final BuiltinFunction lower = new BuiltinFunction("lower") { public String invoke(String self) { return self.toLowerCase(); } }; @SkylarkSignature(name = "upper", objectType = StringModule.class, returnType = String.class, doc = "Returns the upper case version of this string.", parameters = { @Param(name = "self", type = String.class, doc = "This string, to convert to upper case.")}) private static final BuiltinFunction upper = new BuiltinFunction("upper") { public String invoke(String self) { return self.toUpperCase(); } }; /** * For consistency with Python we recognize the same whitespace characters as they do over the * range 0x00-0xFF. See https://hg.python.org/cpython/file/3.6/Objects/unicodetype_db.h#l5738 * This list is a consequence of Unicode character information. * * Note that this differs from Python 2.7, which uses ctype.h#isspace(), and from * java.lang.Character#isWhitespace(), which does not recognize U+00A0. */ private static final String LATIN1_WHITESPACE = ( "\u0009" + "\n" + "\u000B" + "\u000C" + "\r" + "\u001C" + "\u001D" + "\u001E" + "\u001F" + "\u0020" + "\u0085" + "\u00A0" ); private static String stringLStrip(String self, String chars) { CharMatcher matcher = CharMatcher.anyOf(chars); for (int i = 0; i < self.length(); i++) { if (!matcher.matches(self.charAt(i))) { return self.substring(i); } } return ""; // All characters were stripped. } private static String stringRStrip(String self, String chars) { CharMatcher matcher = CharMatcher.anyOf(chars); for (int i = self.length() - 1; i >= 0; i--) { if (!matcher.matches(self.charAt(i))) { return self.substring(0, i + 1); } } return ""; // All characters were stripped. } @SkylarkSignature( name = "lstrip", objectType = StringModule.class, returnType = String.class, doc = "Returns a copy of the string where leading characters that appear in chars" + "are removed." + "
"
            + "\"abcba\".lstrip(\"ba\") == \"cba\""
            + "
", parameters = { @Param(name = "self", type = String.class, doc = "This string."), @Param( name = "chars", type = String.class, noneable = true, doc = "The characters to remove, or all whitespace if None.", defaultValue = "None" ) } ) private static final BuiltinFunction lstrip = new BuiltinFunction("lstrip") { public String invoke(String self, Object charsOrNone) { String chars = charsOrNone != Runtime.NONE ? (String) charsOrNone : LATIN1_WHITESPACE; return stringLStrip(self, chars); } }; @SkylarkSignature( name = "rstrip", objectType = StringModule.class, returnType = String.class, doc = "Returns a copy of the string where trailing characters that appear in chars" + "are removed." + "
"
            + "\"abcbaa\".rstrip(\"ab\") == \"abc\""
            + "
", parameters = { @Param(name = "self", type = String.class, doc = "This string."), @Param( name = "chars", type = String.class, noneable = true, doc = "The characters to remove, or all whitespace if None.", defaultValue = "None" ) } ) private static final BuiltinFunction rstrip = new BuiltinFunction("rstrip") { public String invoke(String self, Object charsOrNone) { String chars = charsOrNone != Runtime.NONE ? (String) charsOrNone : LATIN1_WHITESPACE; return stringRStrip(self, chars); } }; @SkylarkSignature( name = "strip", objectType = StringModule.class, returnType = String.class, doc = "Returns a copy of the string where trailing characters that appear in chars" + "are removed." + "
"
            + "\"aabcbcbaa\".strip(\"ab\") == \"cbc\""
            + "
", parameters = { @Param(name = "self", type = String.class, doc = "This string."), @Param( name = "chars", type = String.class, noneable = true, doc = "The characters to remove, or all whitespace if None.", defaultValue = "None" ) } ) private static final BuiltinFunction strip = new BuiltinFunction("strip") { public String invoke(String self, Object charsOrNone) { String chars = charsOrNone != Runtime.NONE ? (String) charsOrNone : LATIN1_WHITESPACE; return stringLStrip(stringRStrip(self, chars), chars); } }; @SkylarkSignature( name = "replace", objectType = StringModule.class, returnType = String.class, doc = "Returns a copy of the string in which the occurrences " + "of old have been replaced with new, optionally " + "restricting the number of replacements to maxsplit.", parameters = { @Param(name = "self", type = String.class, doc = "This string."), @Param(name = "old", type = String.class, doc = "The string to be replaced."), @Param(name = "new", type = String.class, doc = "The string to replace with."), @Param( name = "maxsplit", type = Integer.class, noneable = true, defaultValue = "None", doc = "The maximum number of replacements." ) }, useLocation = true ) private static final BuiltinFunction replace = new BuiltinFunction("replace") { public String invoke( String self, String oldString, String newString, Object maxSplitO, Location loc) throws EvalException { StringBuffer sb = new StringBuffer(); Integer maxSplit = Type.INTEGER.convertOptional( maxSplitO, "'maxsplit' argument of 'replace'", /*label*/ null, Integer.MAX_VALUE); try { Matcher m = Pattern.compile(oldString, Pattern.LITERAL).matcher(self); for (int i = 0; i < maxSplit && m.find(); i++) { m.appendReplacement(sb, Matcher.quoteReplacement(newString)); } m.appendTail(sb); } catch (IllegalStateException e) { throw new EvalException(loc, e.getMessage() + " in call to replace"); } return sb.toString(); } }; @SkylarkSignature( name = "split", objectType = StringModule.class, returnType = MutableList.class, doc = "Returns a list of all the words in the string, using sep as the separator, " + "optionally limiting the number of splits to maxsplit.", parameters = { @Param(name = "self", type = String.class, doc = "This string."), @Param(name = "sep", type = String.class, doc = "The string to split on."), @Param( name = "maxsplit", type = Integer.class, noneable = true, defaultValue = "None", doc = "The maximum number of splits." ) }, useEnvironment = true, useLocation = true ) private static final BuiltinFunction split = new BuiltinFunction("split") { public MutableList invoke( String self, String sep, Object maxSplitO, Location loc, Environment env) throws EvalException { int maxSplit = Type.INTEGER.convertOptional( maxSplitO, "'split' argument of 'split'", /*label*/ null, -2); // + 1 because the last result is the remainder. The default is -2 so that after +1, // it becomes -1. String[] ss = Pattern.compile(sep, Pattern.LITERAL).split(self, maxSplit + 1); return MutableList.of(env, ss); } }; @SkylarkSignature( name = "rsplit", objectType = StringModule.class, returnType = MutableList.class, doc = "Returns a list of all the words in the string, using sep as the separator, " + "optionally limiting the number of splits to maxsplit. " + "Except for splitting from the right, this method behaves like split().", parameters = { @Param(name = "self", type = String.class, doc = "This string."), @Param(name = "sep", type = String.class, doc = "The string to split on."), @Param( name = "maxsplit", type = Integer.class, noneable = true, defaultValue = "None", doc = "The maximum number of splits." ) }, useEnvironment = true, useLocation = true ) private static final BuiltinFunction rsplit = new BuiltinFunction("rsplit") { @SuppressWarnings("unused") public MutableList invoke( String self, String sep, Object maxSplitO, Location loc, Environment env) throws EvalException { int maxSplit = Type.INTEGER.convertOptional(maxSplitO, "'split' argument of 'split'", null, -1); try { return stringRSplit(self, sep, maxSplit, env); } catch (IllegalArgumentException ex) { throw new EvalException(loc, ex); } } }; /** * Splits the given string into a list of words, using {@code separator} as a delimiter. * *

At most {@code maxSplits} will be performed, going from right to left. * * @param input The input string. * @param separator The separator string. * @param maxSplits The maximum number of splits. Negative values mean unlimited splits. * @return A list of words * @throws IllegalArgumentException */ private static MutableList stringRSplit( String input, String separator, int maxSplits, Environment env) { if (separator.isEmpty()) { throw new IllegalArgumentException("Empty separator"); } if (maxSplits <= 0) { maxSplits = Integer.MAX_VALUE; } LinkedList result = new LinkedList<>(); String[] parts = input.split(Pattern.quote(separator), -1); int sepLen = separator.length(); int remainingLength = input.length(); int splitsSoFar = 0; // Copies parts from the array into the final list, starting at the end (because // it's rsplit), as long as fewer than maxSplits splits are performed. The // last spot in the list is reserved for the remaining string, whose length // has to be tracked throughout the loop. for (int pos = parts.length - 1; (pos >= 0) && (splitsSoFar < maxSplits); --pos) { String current = parts[pos]; result.addFirst(current); ++splitsSoFar; remainingLength -= sepLen + current.length(); } if (splitsSoFar == maxSplits && remainingLength >= 0) { result.addFirst(input.substring(0, remainingLength)); } return new MutableList<>(result, env); } @SkylarkSignature(name = "partition", objectType = StringModule.class, returnType = Tuple.class, doc = "Splits the input string at the first occurrence of the separator " + "sep and returns the resulting partition as a three-element " + "tuple of the form (substring_before, separator, substring_after).", parameters = { @Param(name = "self", type = String.class, doc = "This string."), @Param(name = "sep", type = String.class, defaultValue = "\" \"", doc = "The string to split on, default is space (\" \").")}, useEnvironment = true, useLocation = true) private static final BuiltinFunction partition = new BuiltinFunction("partition") { @SuppressWarnings("unused") public Tuple invoke(String self, String sep, Location loc, Environment env) throws EvalException { return partitionWrapper(self, sep, true, loc); } }; @SkylarkSignature(name = "rpartition", objectType = StringModule.class, returnType = Tuple.class, doc = "Splits the input string at the last occurrence of the separator " + "sep and returns the resulting partition as a three-element " + "tuple of the form (substring_before, separator, substring_after).", parameters = { @Param(name = "self", type = String.class, doc = "This string."), @Param(name = "sep", type = String.class, defaultValue = "\" \"", doc = "The string to split on, default is space (\" \").")}, useEnvironment = true, useLocation = true) private static final BuiltinFunction rpartition = new BuiltinFunction("rpartition") { @SuppressWarnings("unused") public Tuple invoke(String self, String sep, Location loc, Environment env) throws EvalException { return partitionWrapper(self, sep, false, loc); } }; /** * Wraps the stringPartition() method and converts its results and exceptions * to the expected types. * * @param self The input string * @param separator The string to split on * @param forward A flag that controls whether the input string is split around * the first ({@code true}) or last ({@code false}) occurrence of the separator. * @param loc The location that is used for potential exceptions * @return A list with three elements */ private static Tuple partitionWrapper( String self, String separator, boolean forward, Location loc) throws EvalException { try { return Tuple.copyOf(stringPartition(self, separator, forward)); } catch (IllegalArgumentException ex) { throw new EvalException(loc, ex); } } /** * Splits the input string at the {first|last} occurrence of the given separator and returns the * resulting partition as a three-tuple of Strings, contained in a {@code MutableList}. * *

If the input string does not contain the separator, the tuple will consist of the original * input string and two empty strings. * *

This method emulates the behavior of Python's str.partition() and str.rpartition(), * depending on the value of the {@code forward} flag. * * @param input The input string * @param separator The string to split on * @param forward A flag that controls whether the input string is split around the first ({@code * true}) or last ({@code false}) occurrence of the separator. * @return A three-tuple (List) of the form [part_before_separator, separator, * part_after_separator]. */ private static List stringPartition(String input, String separator, boolean forward) { if (separator.isEmpty()) { throw new IllegalArgumentException("Empty separator"); } int partitionSize = 3; ArrayList result = new ArrayList<>(partitionSize); int pos = forward ? input.indexOf(separator) : input.lastIndexOf(separator); if (pos < 0) { for (int i = 0; i < partitionSize; ++i) { result.add(""); } // Following Python's implementation of str.partition() and str.rpartition(), // the input string is copied to either the first or the last position in the // list, depending on the value of the forward flag. result.set(forward ? 0 : partitionSize - 1, input); } else { result.add(input.substring(0, pos)); result.add(separator); // pos + sep.length() is at most equal to input.length(). This worst-case // happens when the separator is at the end of the input string. However, // substring() will return an empty string in this scenario, thus making // any additional safety checks obsolete. result.add(input.substring(pos + separator.length())); } return result; } @SkylarkSignature( name = "capitalize", objectType = StringModule.class, returnType = String.class, doc = "Returns a copy of the string with its first character capitalized and the rest " + "lowercased. This method does not support non-ascii characters.", parameters = {@Param(name = "self", type = String.class, doc = "This string.")} ) private static final BuiltinFunction capitalize = new BuiltinFunction("capitalize") { @SuppressWarnings("unused") public String invoke(String self) throws EvalException { if (self.isEmpty()) { return self; } return Character.toUpperCase(self.charAt(0)) + self.substring(1).toLowerCase(); } }; @SkylarkSignature(name = "title", objectType = StringModule.class, returnType = String.class, doc = "Converts the input string into title case, i.e. every word starts with an " + "uppercase letter while the remaining letters are lowercase. In this " + "context, a word means strictly a sequence of letters. This method does " + "not support supplementary Unicode characters.", parameters = { @Param(name = "self", type = String.class, doc = "This string.")}) private static final BuiltinFunction title = new BuiltinFunction("title") { @SuppressWarnings("unused") public String invoke(String self) throws EvalException { char[] data = self.toCharArray(); boolean previousWasLetter = false; for (int pos = 0; pos < data.length; ++pos) { char current = data[pos]; boolean currentIsLetter = Character.isLetter(current); if (currentIsLetter) { if (previousWasLetter && Character.isUpperCase(current)) { data[pos] = Character.toLowerCase(current); } else if (!previousWasLetter && Character.isLowerCase(current)) { data[pos] = Character.toUpperCase(current); } } previousWasLetter = currentIsLetter; } return new String(data); } }; /** * Common implementation for find, rfind, index, rindex. * @param forward true if we want to return the last matching index. */ private static int stringFind(boolean forward, String self, String sub, int start, Object end, String msg) throws ConversionException { String substr = pythonSubstring(self, start, end, msg); int subpos = forward ? substr.indexOf(sub) : substr.lastIndexOf(sub); start = EvalUtils.clampRangeEndpoint(start, self.length()); return subpos < 0 ? subpos : subpos + start; } @SkylarkSignature(name = "rfind", objectType = StringModule.class, returnType = Integer.class, doc = "Returns the last index where sub is found, " + "or -1 if no such index exists, optionally restricting to " + "[start:end], " + "start being inclusive and end being exclusive.", parameters = { @Param(name = "self", type = String.class, doc = "This string."), @Param(name = "sub", type = String.class, doc = "The substring to find."), @Param(name = "start", type = Integer.class, defaultValue = "0", doc = "Restrict to search from this position."), @Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None", doc = "optional position before which to restrict to search.")}) private static final BuiltinFunction rfind = new BuiltinFunction("rfind") { public Integer invoke(String self, String sub, Integer start, Object end) throws ConversionException { return stringFind(false, self, sub, start, end, "'end' argument to rfind"); } }; @SkylarkSignature(name = "find", objectType = StringModule.class, returnType = Integer.class, doc = "Returns the first index where sub is found, " + "or -1 if no such index exists, optionally restricting to " + "[start:end], " + "start being inclusive and end being exclusive.", parameters = { @Param(name = "self", type = String.class, doc = "This string."), @Param(name = "sub", type = String.class, doc = "The substring to find."), @Param(name = "start", type = Integer.class, defaultValue = "0", doc = "Restrict to search from this position."), @Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None", doc = "optional position before which to restrict to search.")}) private static final BuiltinFunction find = new BuiltinFunction("find") { public Integer invoke(String self, String sub, Integer start, Object end) throws ConversionException { return stringFind(true, self, sub, start, end, "'end' argument to find"); } }; @SkylarkSignature( name = "rindex", objectType = StringModule.class, returnType = Integer.class, doc = "Returns the last index where sub is found, " + "or raises an error if no such index exists, optionally restricting to " + "[start:end], " + "start being inclusive and end being exclusive.", parameters = { @Param(name = "self", type = String.class, doc = "This string."), @Param(name = "sub", type = String.class, doc = "The substring to find."), @Param( name = "start", type = Integer.class, defaultValue = "0", doc = "Restrict to search from this position." ), @Param( name = "end", type = Integer.class, noneable = true, defaultValue = "None", doc = "optional position before which to restrict to search." ) }, useLocation = true ) private static final BuiltinFunction rindex = new BuiltinFunction("rindex") { public Integer invoke(String self, String sub, Integer start, Object end, Location loc) throws EvalException { int res = stringFind(false, self, sub, start, end, "'end' argument to rindex"); if (res < 0) { throw new EvalException(loc, Printer.format("substring %r not found in %r", sub, self)); } return res; } }; @SkylarkSignature( name = "index", objectType = StringModule.class, returnType = Integer.class, doc = "Returns the first index where sub is found, " + "or raises an error if no such index exists, optionally restricting to " + "[start:end], " + "start being inclusive and end being exclusive.", parameters = { @Param(name = "self", type = String.class, doc = "This string."), @Param(name = "sub", type = String.class, doc = "The substring to find."), @Param( name = "start", type = Integer.class, defaultValue = "0", doc = "Restrict to search from this position." ), @Param( name = "end", type = Integer.class, noneable = true, defaultValue = "None", doc = "optional position before which to restrict to search." ) }, useLocation = true ) private static final BuiltinFunction index = new BuiltinFunction("index") { public Integer invoke(String self, String sub, Integer start, Object end, Location loc) throws EvalException { int res = stringFind(true, self, sub, start, end, "'end' argument to index"); if (res < 0) { throw new EvalException(loc, Printer.format("substring %r not found in %r", sub, self)); } return res; } }; @SkylarkSignature(name = "splitlines", objectType = StringModule.class, returnType = SkylarkList.class, doc = "Splits the string at line boundaries ('\\n', '\\r\\n', '\\r') " + "and returns the result as a list.", parameters = { @Param(name = "self", type = String.class, doc = "This string."), @Param(name = "keepends", type = Boolean.class, defaultValue = "False", doc = "Whether the line breaks should be included in the resulting list.")}) private static final BuiltinFunction splitLines = new BuiltinFunction("splitlines") { @SuppressWarnings("unused") public SkylarkList invoke(String self, Boolean keepEnds) throws EvalException { List result = new ArrayList<>(); Matcher matcher = SPLIT_LINES_PATTERN.matcher(self); while (matcher.find()) { String line = matcher.group("line"); String lineBreak = matcher.group("break"); boolean trailingBreak = lineBreak.isEmpty(); if (line.isEmpty() && trailingBreak) { break; } if (keepEnds && !trailingBreak) { result.add(line + lineBreak); } else { result.add(line); } } return SkylarkList.createImmutable(result); } }; private static final Pattern SPLIT_LINES_PATTERN = Pattern.compile("(?.*)(?(\\r\\n|\\r|\\n)?)"); @SkylarkSignature(name = "isalpha", objectType = StringModule.class, returnType = Boolean.class, doc = "Returns True if all characters in the string are alphabetic ([a-zA-Z]) and there is " + "at least one character.", parameters = { @Param(name = "self", type = String.class, doc = "This string.")}) private static final BuiltinFunction isalpha = new BuiltinFunction("isalpha") { @SuppressWarnings("unused") // Called via Reflection public Boolean invoke(String self) throws EvalException { return MethodLibrary.matches(self, MethodLibrary.ALPHA, false); } }; @SkylarkSignature(name = "isalnum", objectType = StringModule.class, returnType = Boolean.class, doc = "Returns True if all characters in the string are alphanumeric ([a-zA-Z0-9]) and there is " + "at least one character.", parameters = {@Param(name = "self", type = String.class, doc = "This string.")}) private static final BuiltinFunction isAlnum = new BuiltinFunction("isalnum") { @SuppressWarnings("unused") // Called via Reflection public Boolean invoke(String self) throws EvalException { return MethodLibrary.matches(self, MethodLibrary.ALNUM, false); } }; @SkylarkSignature(name = "isdigit", objectType = StringModule.class, returnType = Boolean.class, doc = "Returns True if all characters in the string are digits ([0-9]) and there is " + "at least one character.", parameters = {@Param(name = "self", type = String.class, doc = "This string.")}) private static final BuiltinFunction isDigit = new BuiltinFunction("isdigit") { @SuppressWarnings("unused") // Called via Reflection public Boolean invoke(String self) throws EvalException { return MethodLibrary.matches(self, MethodLibrary.DIGIT, false); } }; @SkylarkSignature(name = "isspace", objectType = StringModule.class, returnType = Boolean.class, doc = "Returns True if all characters are white space characters and the string " + "contains at least one character.", parameters = {@Param(name = "self", type = String.class, doc = "This string.")}) private static final BuiltinFunction isSpace = new BuiltinFunction("isspace") { @SuppressWarnings("unused") // Called via Reflection public Boolean invoke(String self) throws EvalException { return MethodLibrary.matches(self, MethodLibrary.SPACE, false); } }; @SkylarkSignature(name = "islower", objectType = StringModule.class, returnType = Boolean.class, doc = "Returns True if all cased characters in the string are lowercase and there is " + "at least one character.", parameters = {@Param(name = "self", type = String.class, doc = "This string.")}) private static final BuiltinFunction isLower = new BuiltinFunction("islower") { @SuppressWarnings("unused") // Called via Reflection public Boolean invoke(String self) throws EvalException { // Python also accepts non-cased characters, so we cannot use LOWER. return MethodLibrary.matches(self, MethodLibrary.UPPER.negate(), true); } }; @SkylarkSignature(name = "isupper", objectType = StringModule.class, returnType = Boolean.class, doc = "Returns True if all cased characters in the string are uppercase and there is " + "at least one character.", parameters = {@Param(name = "self", type = String.class, doc = "This string.")}) private static final BuiltinFunction isUpper = new BuiltinFunction("isupper") { @SuppressWarnings("unused") // Called via Reflection public Boolean invoke(String self) throws EvalException { // Python also accepts non-cased characters, so we cannot use UPPER. return MethodLibrary.matches(self, MethodLibrary.LOWER.negate(), true); } }; @SkylarkSignature(name = "istitle", objectType = StringModule.class, returnType = Boolean.class, doc = "Returns True if the string is in title case and it contains at least one character. " + "This means that every uppercase character must follow an uncased one (e.g. whitespace) " + "and every lowercase character must follow a cased one (e.g. uppercase or lowercase).", parameters = {@Param(name = "self", type = String.class, doc = "This string.")}) private static final BuiltinFunction isTitle = new BuiltinFunction("istitle") { @SuppressWarnings("unused") // Called via Reflection public Boolean invoke(String self) throws EvalException { if (self.isEmpty()) { return false; } // From the Python documentation: "uppercase characters may only follow uncased characters // and lowercase characters only cased ones". char[] data = self.toCharArray(); CharMatcher matcher = CharMatcher.any(); char leftMostCased = ' '; for (int pos = data.length - 1; pos >= 0; --pos) { char current = data[pos]; // 1. Check condition that was determined by the right neighbor. if (!matcher.matches(current)) { return false; } // 2. Determine condition for the left neighbor. if (LOWER.matches(current)) { matcher = CASED; } else if (UPPER.matches(current)) { matcher = CASED.negate(); } else { matcher = CharMatcher.any(); } // 3. Store character if it is cased. if (CASED.matches(current)) { leftMostCased = current; } } // The leftmost cased letter must be uppercase. If leftMostCased is not a cased letter here, // then the string doesn't have any cased letter, so UPPER.test will return false. return UPPER.matches(leftMostCased); } }; private static boolean matches( String str, CharMatcher matcher, boolean requiresAtLeastOneCasedLetter) { if (str.isEmpty()) { return false; } else if (!requiresAtLeastOneCasedLetter) { return matcher.matchesAllOf(str); } int casedLetters = 0; for (char current : str.toCharArray()) { if (!matcher.matches(current)) { return false; } else if (requiresAtLeastOneCasedLetter && CASED.matches(current)) { ++casedLetters; } } return casedLetters > 0; } private static final CharMatcher DIGIT = CharMatcher.javaDigit(); private static final CharMatcher LOWER = CharMatcher.inRange('a', 'z'); private static final CharMatcher UPPER = CharMatcher.inRange('A', 'Z'); private static final CharMatcher ALPHA = LOWER.or(UPPER); private static final CharMatcher ALNUM = ALPHA.or(DIGIT); private static final CharMatcher CASED = ALPHA; private static final CharMatcher SPACE = CharMatcher.whitespace(); @SkylarkSignature(name = "count", objectType = StringModule.class, returnType = Integer.class, doc = "Returns the number of (non-overlapping) occurrences of substring sub in " + "string, optionally restricting to [start:end], " + "start being inclusive and end being exclusive.", parameters = { @Param(name = "self", type = String.class, doc = "This string."), @Param(name = "sub", type = String.class, doc = "The substring to count."), @Param(name = "start", type = Integer.class, defaultValue = "0", doc = "Restrict to search from this position."), @Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None", doc = "optional position before which to restrict to search.")}) private static final BuiltinFunction count = new BuiltinFunction("count") { public Integer invoke(String self, String sub, Integer start, Object end) throws ConversionException { String str = pythonSubstring(self, start, end, "'end' operand of 'find'"); if (sub.isEmpty()) { return str.length() + 1; } int count = 0; int index = -1; while ((index = str.indexOf(sub)) >= 0) { count++; str = str.substring(index + sub.length()); } return count; } }; @SkylarkSignature(name = "endswith", objectType = StringModule.class, returnType = Boolean.class, doc = "Returns True if the string ends with sub, " + "otherwise False, optionally restricting to [start:end], " + "start being inclusive and end being exclusive.", parameters = { @Param(name = "self", type = String.class, doc = "This string."), @Param(name = "sub", type = String.class, doc = "The substring to check."), @Param(name = "start", type = Integer.class, defaultValue = "0", doc = "Test beginning at this position."), @Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None", doc = "optional position at which to stop comparing.")}) private static final BuiltinFunction endswith = new BuiltinFunction("endswith") { public Boolean invoke(String self, String sub, Integer start, Object end) throws ConversionException { return pythonSubstring(self, start, end, "'end' operand of 'endswith'").endsWith(sub); } }; // In Python, formatting is very complex. // We handle here the simplest case which provides most of the value of the function. // https://docs.python.org/3/library/string.html#formatstrings @SkylarkSignature( name = "format", objectType = StringModule.class, returnType = String.class, doc = "Perform string interpolation. Format strings contain replacement fields " + "surrounded by curly braces {}. Anything that is not contained " + "in braces is considered literal text, which is copied unchanged to the output." + "If you need to include a brace character in the literal text, it can be " + "escaped by doubling: {{ and }}" + "A replacement field can be either a name, a number, or empty. Values are " + "converted to strings using the str function." + "

"
            + "# Access in order:\n"
            + "\"{} < {}\".format(4, 5) == \"4 < 5\"\n"
            + "# Access by position:\n"
            + "\"{1}, {0}\".format(2, 1) == \"1, 2\"\n"
            + "# Access by name:\n"
            + "\"x{key}x\".format(key = 2) == \"x2x\"
\n", parameters = { @Param(name = "self", type = String.class, doc = "This string."), }, extraPositionals = @Param( name = "args", type = SkylarkList.class, defaultValue = "()", doc = "List of arguments." ), extraKeywords = @Param( name = "kwargs", type = SkylarkDict.class, defaultValue = "{}", doc = "Dictionary of arguments." ), useLocation = true, useEnvironment = true ) private static final BuiltinFunction format = new BuiltinFunction("format") { @SuppressWarnings("unused") public String invoke( String self, SkylarkList args, SkylarkDict kwargs, Location loc, Environment env) throws EvalException { return new FormatParser(env, loc) .format( self, args.getImmutableList(), kwargs.getContents(String.class, Object.class, "kwargs")); } }; @SkylarkSignature(name = "startswith", objectType = StringModule.class, returnType = Boolean.class, doc = "Returns True if the string starts with sub, " + "otherwise False, optionally restricting to [start:end], " + "start being inclusive and end being exclusive.", parameters = { @Param(name = "self", type = String.class, doc = "This string."), @Param(name = "sub", type = String.class, doc = "The substring to check."), @Param(name = "start", type = Integer.class, defaultValue = "0", doc = "Test beginning at this position."), @Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None", doc = "Stop comparing at this position.")}) private static final BuiltinFunction startswith = new BuiltinFunction("startswith") { public Boolean invoke(String self, String sub, Integer start, Object end) throws ConversionException { return pythonSubstring(self, start, end, "'end' operand of 'startswith'").startsWith(sub); } }; @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 new MutableList<>( EvalUtils.SKYLARK_COMPARATOR.sortedCopy(EvalUtils.toCollection(self, loc, env)), 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."); } LinkedList tmpList = new LinkedList<>(); for (Object element : EvalUtils.toIterable(sequence, loc, env)) { tmpList.addFirst(element); } return new MutableList<>(tmpList, env); } }; @SkylarkSignature( name = "append", objectType = MutableList.class, returnType = Runtime.NoneType.class, doc = "Adds an item to the end of the list.", parameters = { @Param(name = "self", type = MutableList.class, doc = "This list."), @Param(name = "item", type = Object.class, doc = "Item to add at the end.") }, useLocation = true, useEnvironment = true ) private static final BuiltinFunction append = new BuiltinFunction("append") { public Runtime.NoneType invoke( MutableList self, Object item, Location loc, Environment env) throws EvalException { self.add(item, loc, env.mutability()); return Runtime.NONE; } }; @SkylarkSignature( name = "insert", objectType = MutableList.class, returnType = Runtime.NoneType.class, doc = "Inserts an item at a given position.", parameters = { @Param(name = "self", type = MutableList.class, doc = "This list."), @Param(name = "index", type = Integer.class, doc = "The index of the given position."), @Param(name = "item", type = Object.class, doc = "The item.") }, useLocation = true, useEnvironment = true ) private static final BuiltinFunction insert = new BuiltinFunction("insert") { public Runtime.NoneType invoke( MutableList self, Integer index, Object item, Location loc, Environment env) throws EvalException { self.add(EvalUtils.clampRangeEndpoint(index, self.size()), item, loc, env.mutability()); return Runtime.NONE; } }; @SkylarkSignature( name = "extend", objectType = MutableList.class, returnType = Runtime.NoneType.class, doc = "Adds all items to the end of the list.", parameters = { @Param(name = "self", type = MutableList.class, doc = "This list."), @Param(name = "items", type = SkylarkList.class, doc = "Items to add at the end.") }, useLocation = true, useEnvironment = true ) private static final BuiltinFunction extend = new BuiltinFunction("extend") { public Runtime.NoneType invoke( MutableList self, SkylarkList items, Location loc, Environment env) throws EvalException { self.addAll(items, loc, env.mutability()); return Runtime.NONE; } }; @SkylarkSignature( name = "index", objectType = MutableList.class, returnType = Integer.class, doc = "Returns the index in the list of the first item whose value is x. " + "It is an error if there is no such item.", parameters = { @Param(name = "self", type = MutableList.class, doc = "This list."), @Param(name = "x", type = Object.class, doc = "The object to search.") }, useLocation = true ) private static final BuiltinFunction listIndex = new BuiltinFunction("index") { public Integer invoke(MutableList self, Object x, Location loc) throws EvalException { int i = 0; for (Object obj : self) { if (obj.equals(x)) { return i; } i++; } throw new EvalException(loc, Printer.format("item %r not found in list", x)); } }; @SkylarkSignature( name = "remove", objectType = MutableList.class, returnType = Runtime.NoneType.class, doc = "Removes the first item from the list whose value is x. " + "It is an error if there is no such item.", parameters = { @Param(name = "self", type = MutableList.class, doc = "This list."), @Param(name = "x", type = Object.class, doc = "The object to remove.") }, useLocation = true, useEnvironment = true ) private static final BuiltinFunction listRemove = new BuiltinFunction("remove") { public Runtime.NoneType invoke(MutableList self, Object x, Location loc, Environment env) throws EvalException { for (int i = 0; i < self.size(); i++) { if (self.get(i).equals(x)) { self.remove(i, loc, env.mutability()); return Runtime.NONE; } } throw new EvalException(loc, Printer.format("item %r not found in list", x)); } }; @SkylarkSignature( name = "pop", objectType = MutableList.class, returnType = Object.class, doc = "Removes the item at the given position in the list, and returns it. " + "If no index is specified, " + "it removes and returns the last item in the list.", parameters = { @Param(name = "self", type = MutableList.class, doc = "This list."), @Param( name = "i", type = Integer.class, noneable = true, defaultValue = "None", doc = "The index of the item." ) }, useLocation = true, useEnvironment = true ) private static final BuiltinFunction listPop = new BuiltinFunction("pop") { public Object invoke(MutableList self, Object i, Location loc, Environment env) throws EvalException { int arg = i == Runtime.NONE ? -1 : (Integer) i; int index = EvalUtils.getSequenceIndex(arg, self.size(), loc); Object result = self.get(index); self.remove(index, loc, env.mutability()); return result; } }; @SkylarkSignature( name = "pop", objectType = SkylarkDict.class, returnType = Object.class, doc = "Removes a key from the dict, and returns the associated value. " + "If entry with that key was found, return the specified default value;" + "if no default value was specified, fail instead.", parameters = { @Param(name = "self", type = SkylarkDict.class, doc = "This dict."), @Param(name = "key", type = Object.class, doc = "The key."), @Param(name = "default", type = Object.class, defaultValue = "unbound", doc = "a default value if the key is absent."), }, useLocation = true, useEnvironment = true ) private static final BuiltinFunction dictPop = new BuiltinFunction("pop") { public Object invoke(SkylarkDict self, Object key, Object defaultValue, Location loc, Environment env) throws EvalException { Object value = self.get(key); if (value != null) { self.remove(key, loc, env); return value; } if (defaultValue != Runtime.UNBOUND) { return defaultValue; } throw new EvalException(loc, Printer.format("KeyError: %r", key)); } }; @SkylarkSignature( name = "popitem", objectType = SkylarkDict.class, returnType = Tuple.class, doc = "Remove and return an arbitrary (key, value) pair from the dictionary. " + "popitem() is useful to destructively iterate over a dictionary, " + "as often used in set algorithms. " + "If the dictionary is empty, calling popitem() fails. " + "It is deterministic which pair is returned.", parameters = { @Param(name = "self", type = SkylarkDict.class, doc = "This dict.") }, useLocation = true, useEnvironment = true ) private static final BuiltinFunction dictPopItem = new BuiltinFunction("popitem") { public Tuple invoke(SkylarkDict self, Location loc, Environment env) throws EvalException { if (self.isEmpty()) { throw new EvalException(loc, "popitem(): dictionary is empty"); } Object key = self.firstKey(); Object value = self.get(key); self.remove(key, loc, env); return Tuple.of(key, value); } }; @SkylarkSignature( name = "clear", objectType = SkylarkDict.class, returnType = Runtime.NoneType.class, doc = "Remove all items from the dictionary.", parameters = { @Param(name = "self", type = SkylarkDict.class, doc = "This dict.") }, useLocation = true, useEnvironment = true ) private static final BuiltinFunction dictClear = new BuiltinFunction("clear") { public Runtime.NoneType invoke(SkylarkDict self, Location loc, Environment env) throws EvalException { self.clear(loc, env); return Runtime.NONE; } }; @SkylarkSignature( name = "setdefault", objectType = SkylarkDict.class, returnType = Object.class, doc = "If key is in the dictionary, return its value. " + "If not, insert key with a value of default " + "and return default. " + "default defaults to None.", parameters = { @Param(name = "self", type = SkylarkDict.class, doc = "This dict."), @Param(name = "key", type = Object.class, doc = "The key."), @Param( name = "default", type = Object.class, defaultValue = "None", doc = "a default value if the key is absent." ), }, useLocation = true, useEnvironment = true ) private static final BuiltinFunction dictSetDefault = new BuiltinFunction("setdefault") { public Object invoke( SkylarkDict self, Object key, Object defaultValue, Location loc, Environment env) throws EvalException { Object value = self.get(key); if (value != null) { return value; } self.put(key, defaultValue, loc, env); return defaultValue; } }; @SkylarkSignature( name = "update", objectType = SkylarkDict.class, returnType = Runtime.NoneType.class, doc = "Update the dictionary with the key/value pairs from other, overwriting existing keys.", parameters = { @Param(name = "self", type = SkylarkDict.class, doc = "This dict."), @Param(name = "other", type = SkylarkDict.class, doc = "The values to add."), }, useLocation = true, useEnvironment = true ) private static final BuiltinFunction dictUpdate = new BuiltinFunction("update") { public Runtime.NoneType invoke( SkylarkDict self, SkylarkDict other, Location loc, Environment env) throws EvalException { self.putAll(other, loc, env); return Runtime.NONE; } }; @SkylarkSignature( name = "values", objectType = SkylarkDict.class, returnType = MutableList.class, doc = "Returns the list of values:" + "
"
            + "{2: \"a\", 4: \"b\", 1: \"c\"}.values() == [\"a\", \"b\", \"c\"]
\n", parameters = {@Param(name = "self", type = SkylarkDict.class, doc = "This dict.")}, useEnvironment = true ) private static final BuiltinFunction values = new BuiltinFunction("values") { public MutableList invoke(SkylarkDict self, Environment env) throws EvalException { return new MutableList<>(self.values(), env); } }; @SkylarkSignature( name = "items", objectType = SkylarkDict.class, returnType = MutableList.class, doc = "Returns the list of key-value tuples:" + "
"
            + "{2: \"a\", 4: \"b\", 1: \"c\"}.items() == [(2, \"a\"), (4, \"b\"), (1, \"c\")]"
            + "
\n", parameters = {@Param(name = "self", type = SkylarkDict.class, doc = "This dict.")}, useEnvironment = true ) private static final BuiltinFunction items = new BuiltinFunction("items") { public MutableList invoke(SkylarkDict self, Environment env) throws EvalException { List list = Lists.newArrayListWithCapacity(self.size()); for (Map.Entry entries : self.entrySet()) { list.add(Tuple.of(entries.getKey(), entries.getValue())); } return new MutableList<>(list, env); } }; @SkylarkSignature(name = "keys", objectType = SkylarkDict.class, returnType = MutableList.class, doc = "Returns the list of keys:" + "
{2: \"a\", 4: \"b\", 1: \"c\"}.keys() == [2, 4, 1]"
          + "
\n", parameters = { @Param(name = "self", type = SkylarkDict.class, doc = "This dict.")}, useEnvironment = true) private static final BuiltinFunction keys = new BuiltinFunction("keys") { // Skylark will only call this on a dict; and // allowed keys are all Comparable... if not mutually, it's OK to get a runtime exception. @SuppressWarnings("unchecked") public MutableList invoke(SkylarkDict self, Environment env) throws EvalException { List list = Lists.newArrayListWithCapacity(self.size()); for (Map.Entry entries : self.entrySet()) { list.add(entries.getKey()); } return new MutableList(list, env); } }; @SkylarkSignature(name = "get", objectType = SkylarkDict.class, doc = "Returns the value for key if key is in the dictionary, " + "else default. If default is not given, it defaults to " + "None, so that this method never throws an error.", parameters = { @Param(name = "self", doc = "This dict."), @Param(name = "key", doc = "The key to look for."), @Param(name = "default", defaultValue = "None", doc = "The default value to use (instead of None) if the key is not found.")}) private static final BuiltinFunction get = new BuiltinFunction("get") { public Object invoke(SkylarkDict self, Object key, Object defaultValue) { if (self.containsKey(key)) { return self.get(key); } return defaultValue; } }; @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 new MutableList<>(EvalUtils.toCollection(x, loc, env), 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.")}, useEnvironment = true ) private static final BuiltinFunction str = new BuiltinFunction("str") { public String invoke(Object x, Environment env) { return Printer.getPrinter(env).str(x).toString(); } }; @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.")}, useEnvironment = true ) private static final BuiltinFunction repr = new BuiltinFunction("repr") { public String invoke(Object x, Environment env) { return Printer.getPrinter(env).repr(x).toString(); } }; @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 = "Converts a value to int. " + "If the argument is a string, it is converted using the given base and raises an " + "error if the conversion fails. " + "The base can be between 2 and 36 (inclusive) and defaults to 10. " + "The value can be prefixed with 0b/0o/ox to represent values in base 2/8/16. " + "If such a prefix is present, a base of 0 can be used to automatically determine the " + "correct base: " + "
int(\"0xFF\", 0) == int(\"0xFF\", 16) == 255
" + "If the argument is a bool, it returns 0 (False) or 1 (True). " + "If the argument is an int, it is simply returned." + "
int(\"123\") == 123
", parameters = { @Param(name = "x", type = Object.class, doc = "The string to convert."), @Param( name = "base", type = Integer.class, defaultValue = "10", doc = "The base of the 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, Integer base, Location loc) throws EvalException { if (x instanceof String) { return fromString(x, loc, base); } else { if (base != 10) { 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(Object x, Location loc, int base) throws EvalException { String value = (String) x; String prefix = getIntegerPrefix(value); if (!prefix.isEmpty()) { value = value.substring(prefix.length()); int expectedBase = intPrefixes.get(prefix); if (base == 0) { // Similar to Python, base 0 means "derive the base from the prefix". base = expectedBase; } else if (base != expectedBase) { throw new EvalException( loc, Printer.format("invalid literal for int() with base %d: %r", base, x)); } } if (base < 2 || base > 36) { throw new EvalException(loc, "int() base must be >= 2 and <= 36"); } try { return Integer.parseInt(value, base); } catch (NumberFormatException e) { throw new EvalException( loc, Printer.format("invalid literal for int() with base %d: %r", base, x)); } } private String getIntegerPrefix(String value) { value = value.toLowerCase(); for (String prefix : intPrefixes.keySet()) { if (value.startsWith(prefix)) { return prefix; } } return ""; } }; @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. Note that the iteration order for dictionaries is deterministic but " + "unspecified, and not necessarily related to the order in which keys are given to " + "this function.", 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; List> result = Lists.newArrayList(); for (Object obj : input) { result.add(Tuple.of(count, obj)); count++; } return new MutableList<>(result, env); } }; @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 = MutableList.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 MutableList 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"); } ArrayList result = Lists.newArrayList(); if (step > 0) { int size = (stop - start) / step; result.ensureCapacity(size); while (start < stop) { result.add(start); start += step; } } else { int size = (start - stop) / step; result.ensureCapacity(size); while (start > stop) { result.add(start); start += step; } } return new MutableList<>(result, 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; } return 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 { Object result = DotExpression.eval(obj, name, loc, env); if (result == null) { // 'Real' describes methods with structField() == false. Because DotExpression.eval // returned null in this case, we know that structField() cannot return true. boolean isRealMethod = hasMethod(obj, name); if (defaultValue != Runtime.UNBOUND) { return defaultValue; } throw new EvalException( loc, Printer.format( "object of type '%s' has no attribute %r%s", EvalUtils.getDataTypeName(obj), name, isRealMethod ? ", however, a method of that name exists" : "")); } return result; } }; /** * Returns whether the given object has a method with the given name. */ private static boolean hasMethod(Object obj, String name) throws EvalException { if (Runtime.getFunctionNames(obj.getClass()).contains(name)) { return true; } return FuncallExpression.getMethodNames(obj.getClass()).contains(name); } @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).getKeys()); } fields.addAll(Runtime.getFunctionNames(object.getClass())); fields.addAll(FuncallExpression.getMethodNames(object.getClass())); return new MutableList<>(fields, env); } }; @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 output. It will be prefixed with the string " + "\"WARNING\" and the location (file and line number) of this call. It can be " + "used for debugging." + "

Using print in production code is discouraged due to the spam it " + "creates for users. For deprecations, prefer a hard error using " + "fail() when 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((Object o) -> Printer.getPrinter(env).str(o).toString()) .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.warn(loc, msg)); return Runtime.NONE; } }; @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(); } List> 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 new MutableList<>(result, env); } }; /** Skylark String module. */ @SkylarkModule( name = "string", category = SkylarkModuleCategory.BUILTIN, doc = "A language built-in type to support strings. " + "Examples of string literals:
" + "
a = 'abc\\ndef'\n"
            + "b = \"ab'cd\"\n"
            + "c = \"\"\"multiline string\"\"\"\n"
            + "\n"
            + "# Strings support slicing (negative index starts from the end):\n"
            + "x = \"hello\"[2:4]  # \"ll\"\n"
            + "y = \"hello\"[1:-1]  # \"ell\"\n"
            + "z = \"hello\"[:4]  # \"hell\""
            + "# Slice steps can be used, too:\n"
            + "s = \"hello\"[::2] # \"hlo\"\n"
            + "t = \"hello\"[3:0:-1] # \"lle\"\n
" + "Strings are iterable and support the in operator. Examples:
" + "
\"bc\" in \"abcd\"   # evaluates to True\n"
            + "x = [s for s in \"abc\"]  # x == [\"a\", \"b\", \"c\"]
\n" + "Implicit concatenation of strings is not allowed; use the + " + "operator instead." ) static final class StringModule {} /** 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 {} static final List defaultGlobalFunctions = ImmutableList.of( all, any, bool, dict, dir, fail, getattr, hasattr, hash, enumerate, int_, len, list, max, min, print, range, repr, reversed, sorted, str, tuple, zip); static { SkylarkSignatureProcessor.configureSkylarkFunctions(MethodLibrary.class); } }