blob: b0dc7e4abdd71b0b1821fcf34ee081450decf599 [file] [log] [blame]
// Copyright 2014 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package 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;
import javax.annotation.Nullable;
/** 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:<br>"
+ "<pre class=\"language-python\">\"|\".join([\"a\", \"b\", \"c\"]) == \"a|b|c\"</pre>",
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 <code>chars</code>"
+ "are removed."
+ "<pre class=\"language-python\">"
+ "\"abcba\".lstrip(\"ba\") == \"cba\""
+ "</pre>",
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 <code>chars</code>"
+ "are removed."
+ "<pre class=\"language-python\">"
+ "\"abcbaa\".rstrip(\"ab\") == \"abc\""
+ "</pre>",
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 <code>chars</code>"
+ "are removed."
+ "<pre class=\"language-python\">"
+ "\"aabcbcbaa\".strip(\"ab\") == \"cbc\""
+ "</pre>",
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 <code>old</code> have been replaced with <code>new</code>, optionally "
+ "restricting the number of replacements to <code>maxsplit</code>.",
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 <code>sep</code> as the separator, "
+ "optionally limiting the number of splits to <code>maxsplit</code>.",
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<String> 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 <code>sep</code> as the separator, "
+ "optionally limiting the number of splits to <code>maxsplit</code>. "
+ "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<String> 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.
*
* <p>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<String> 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<String> 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 MutableList.copyOf(env, result);
}
@SkylarkSignature(name = "partition", objectType = StringModule.class,
returnType = Tuple.class,
doc = "Splits the input string at the first occurrence of the separator "
+ "<code>sep</code> 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<String> 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 "
+ "<code>sep</code> 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<String> 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<String> 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}.
*
* <p>If the input string does not contain the separator, the tuple will consist of the original
* input string and two empty strings.
*
* <p>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<String> stringPartition(String input, String separator, boolean forward) {
if (separator.isEmpty()) {
throw new IllegalArgumentException("Empty separator");
}
int partitionSize = 3;
ArrayList<String> 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 <code>sub</code> is found, "
+ "or -1 if no such index exists, optionally restricting to "
+ "[<code>start</code>:<code>end</code>], "
+ "<code>start</code> being inclusive and <code>end</code> 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 <code>sub</code> is found, "
+ "or -1 if no such index exists, optionally restricting to "
+ "[<code>start</code>:<code>end]</code>, "
+ "<code>start</code> being inclusive and <code>end</code> 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 <code>sub</code> is found, "
+ "or raises an error if no such index exists, optionally restricting to "
+ "[<code>start</code>:<code>end</code>], "
+ "<code>start</code> being inclusive and <code>end</code> 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 <code>sub</code> is found, "
+ "or raises an error if no such index exists, optionally restricting to "
+ "[<code>start</code>:<code>end]</code>, "
+ "<code>start</code> being inclusive and <code>end</code> 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<String> invoke(String self, Boolean keepEnds) throws EvalException {
List<String> 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("(?<line>.*)(?<break>(\\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 <code>sub</code> in "
+ "string, optionally restricting to [<code>start</code>:<code>end</code>], "
+ "<code>start</code> being inclusive and <code>end</code> 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 <code>sub</code>, "
+ "otherwise False, optionally restricting to [<code>start</code>:<code>end</code>], "
+ "<code>start</code> being inclusive and <code>end</code> 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 <code>{}</code>. 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: <code>{{</code> and <code>}}</code>"
+ "A replacement field can be either a name, a number, or empty. Values are "
+ "converted to strings using the <a href=\"globals.html#str\">str</a> function."
+ "<pre class=\"language-python\">"
+ "# 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\"</pre>\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
)
private static final BuiltinFunction format =
new BuiltinFunction("format") {
@SuppressWarnings("unused")
public String invoke(
String self,
SkylarkList<Object> args,
SkylarkDict<?, ?> kwargs,
Location loc)
throws EvalException {
return new FormatParser(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 <code>sub</code>, "
+ "otherwise False, optionally restricting to [<code>start</code>:<code>end</code>], "
+ "<code>start</code> being inclusive and <code>end</code> 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). "
+ "<pre class=\"language-python\">min(2, 5, 4) == 2\n"
+ "min([5, 6, 3]) == 3</pre>",
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). "
+ "<pre class=\"language-python\">max(2, 5, 4) == 5\n"
+ "max([5, 6, 3]) == 6</pre>",
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<Object> 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 <a href=\"#bool\">bool</a> function."
+ "<pre class=\"language-python\">all([\"hello\", 3, True]) == True\n"
+ "all([-1, 0, 1]) == False</pre>",
parameters = {
@Param(name = "elements", 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 <a href=\"#bool\">bool</a> function."
+ "<pre class=\"language-python\">any([-1, 0, 1]) == True\n"
+ "any([False, 0, \"\"]) == False</pre>",
parameters = {
@Param(name = "elements", 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)."
+ "<pre class=\"language-python\">sorted([3, 5, 4]) == [3, 4, 5]</pre>",
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."
+ "<pre class=\"language-python\">reversed([3, 5, 4]) == [4, 5, 3]</pre>",
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<Object> tmpList = new LinkedList<>();
for (Object element : EvalUtils.toIterable(sequence, loc, env)) {
tmpList.addFirst(element);
}
return MutableList.copyOf(env, tmpList);
}
};
@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<Object> 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<Object> 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<Object> self, SkylarkList<Object> 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 <code>index</code> 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 <code>key</code> from the dict, and returns the associated value. "
+ "If entry with that key was found, return the specified <code>default</code> 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<Object, Object> self, Object key, Object defaultValue,
Location loc, Environment env)
throws EvalException {
Object value = self.get(key);
if (value != null) {
self.remove(key, loc, env.mutability());
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 <code>(key, value)</code> pair from the dictionary. "
+ "<code>popitem()</code> is useful to destructively iterate over a dictionary, "
+ "as often used in set algorithms. "
+ "If the dictionary is empty, calling <code>popitem()</code> 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<Object> invoke(SkylarkDict<Object, Object> self,
Location loc, Environment env)
throws EvalException {
if (self.isEmpty()) {
throw new EvalException(loc, "popitem(): dictionary is empty");
}
Object key = self.keySet().iterator().next();
Object value = self.get(key);
self.remove(key, loc, env.mutability());
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<Object, Object> self,
Location loc, Environment env)
throws EvalException {
self.clear(loc, env.mutability());
return Runtime.NONE;
}
};
@SkylarkSignature(
name = "setdefault",
objectType = SkylarkDict.class,
returnType = Object.class,
doc =
"If <code>key</code> is in the dictionary, return its value. "
+ "If not, insert key with a value of <code>default</code> "
+ "and return <code>default</code>. "
+ "<code>default</code> defaults to <code>None</code>.",
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<Object, Object> 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<Object, Object> self,
SkylarkDict<Object, Object> other,
Location loc,
Environment env)
throws EvalException {
self.putAll(other, loc, env.mutability());
return Runtime.NONE;
}
};
@SkylarkSignature(
name = "values",
objectType = SkylarkDict.class,
returnType = MutableList.class,
doc =
"Returns the list of values:"
+ "<pre class=\"language-python\">"
+ "{2: \"a\", 4: \"b\", 1: \"c\"}.values() == [\"a\", \"b\", \"c\"]</pre>\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 MutableList.copyOf(env, self.values());
}
};
@SkylarkSignature(
name = "items",
objectType = SkylarkDict.class,
returnType = MutableList.class,
doc =
"Returns the list of key-value tuples:"
+ "<pre class=\"language-python\">"
+ "{2: \"a\", 4: \"b\", 1: \"c\"}.items() == [(2, \"a\"), (4, \"b\"), (1, \"c\")]"
+ "</pre>\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 {
ArrayList<Object> list = Lists.newArrayListWithCapacity(self.size());
for (Map.Entry<?, ?> entries : self.entrySet()) {
list.add(Tuple.of(entries.getKey(), entries.getValue()));
}
return MutableList.wrapUnsafe(env, list);
}
};
@SkylarkSignature(name = "keys", objectType = SkylarkDict.class,
returnType = MutableList.class,
doc = "Returns the list of keys:"
+ "<pre class=\"language-python\">{2: \"a\", 4: \"b\", 1: \"c\"}.keys() == [2, 4, 1]"
+ "</pre>\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 {
ArrayList<Object> list = Lists.newArrayListWithCapacity(self.size());
for (Map.Entry<?, ?> entries : self.entrySet()) {
list.add(entries.getKey());
}
return MutableList.wrapUnsafe(env, list);
}
};
@SkylarkSignature(name = "get", objectType = SkylarkDict.class,
doc = "Returns the value for <code>key</code> if <code>key</code> is in the dictionary, "
+ "else <code>default</code>. If <code>default</code> is not given, it defaults to "
+ "<code>None</code>, 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."
+ "<pre class=\"language-python\">tuple([1, 2]) == (1, 2)\n"
+ "tuple((2, 3, 2)) == (2, 3, 2)\n"
+ "tuple({5: \"a\", 2: \"b\", 4: \"c\"}) == (5, 2, 4)</pre>",
parameters = {@Param(name = "x", 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."
+ "<pre class=\"language-python\">list([1, 2]) == [1, 2]\n"
+ "list((2, 3, 2)) == [2, 3, 2]\n"
+ "list({5: \"a\", 2: \"b\", 4: \"c\"}) == [5, 2, 4]</pre>",
parameters = {@Param(name = "x", 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(<depset>.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."
+ "<pre class=\"language-python\">str(\"ab\") == \"ab\"\n"
+ "str(8) == \"8\"</pre>",
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.<br>"
+ "<pre class=\"language-python\">repr(\"ab\") == '\"ab\"'</pre>",
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 <code>False</code> if the object is <code>None</code>, <code>False"
+ "</code>, an empty string (<code>\"\"</code>), the number <code>0</code>, or an "
+ "empty collection (e.g. <code>()</code>, <code>[]</code>). "
+ "Otherwise, it returns <code>True</code>.",
parameters = {@Param(name = "x", 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: "
+ "<pre class=\"language-python\">int(\"0xFF\", 0) == int(\"0xFF\", 16) == 255</pre>"
+ "If the argument is a bool, it returns 0 (False) or 1 (True). "
+ "If the argument is an int, it is simply returned."
+ "<pre class=\"language-python\">int(\"123\") == 123</pre>",
parameters = {
@Param(name = "x", type = Object.class, doc = "The string to convert."),
@Param(
name = "base",
type = Object.class,
defaultValue = "unbound",
doc = "The base to use to interpret a string value; defaults to 10. 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<String, Integer> 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 prefix = getIntegerPrefix(string);
String digits;
if (prefix == null) {
// Nothing to strip. Infer base 10 if it was unknown (0).
digits = string;
if (base == 0) {
base = 10;
}
} else {
// Strip prefix. Infer base from prefix if unknown (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, string));
}
}
if (base < 2 || base > 36) {
throw new EvalException(loc, "int() base must be >= 2 and <= 36");
}
try {
return Integer.parseInt(digits, base);
} catch (NumberFormatException e) {
throw new EvalException(
loc, Printer.format("invalid literal for int() with base %d: %r", base, string));
}
}
@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 <a href=\"dict.html\">dictionary</a> from an optional positional "
+ "argument and an optional set of keyword arguments. In the case where the same key "
+ "is given multiple times, the last value will be used. Entries supplied via keyword "
+ "arguments are considered to come after entries supplied via the positional "
+ "argument. 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<String, Object> 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<Object, Object> getDictFromArgs(
Object args, Location loc, Environment env) throws EvalException {
SkylarkDict<Object, Object> result = SkylarkDict.of(env);
int pos = 0;
for (Object element : Type.OBJECT_LIST.convert(args, "parameter args in dict()")) {
List<Object> pair = convertToPair(element, pos, loc);
result.put(pair.get(0), pair.get(1), loc, env);
++pos;
}
return result;
}
private List<Object> convertToPair(Object element, int pos, Location loc)
throws EvalException {
try {
List<Object> 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<pre class=\"language-python\">"
+ "enumerate([24, 21, 84]) == [(0, 24), (1, 21), (2, 84)]</pre>\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<SkylarkList<?>> 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 <code>String.hashCode()</code>, namely: "
+ "<pre class=\"language-python\">s[0] * (31^(n-1)) + s[1] * (31^(n-2)) + ... + s[n-1]"
+ "</pre> Hashing of values besides strings is not currently supported.",
// Deterministic hashing is important for the consistency of builds, hence why we
// promise a specific algorithm. This is in contrast to Java (Object.hashCode()) and
// Python, which promise stable hashing only within a given execution of the program.
parameters = {@Param(name = "value", 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 <code>start</code> to <code>stop</code>, using a "
+ "<code>step</code> increment. If a single argument is provided, items will "
+ "range from 0 to that element."
+ "<pre class=\"language-python\">range(4) == [0, 1, 2, 3]\n"
+ "range(3, 9, 2) == [3, 5, 7]\n"
+ "range(3, 0, -1) == [3, 2, 1]</pre>",
parameters = {
@Param(
name = "start_or_stop",
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 <i>not</i> to be included in the resulting "
+ "list; generation of the list stops before <code>stop</code> 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<Integer> result = new ArrayList<>(Math.abs((stop - start) / step));
if (step > 0) {
while (start < stop) {
result.add(start);
start += step;
}
} else {
while (start > stop) {
result.add(start);
start += step;
}
}
return MutableList.wrapUnsafe(env, result);
}
};
/** 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 <code>x</code> has an attribute or method of the given "
+ "<code>name</code>, otherwise False. Example:<br>"
+ "<pre class=\"language-python\">hasattr(ctx.attr, \"myattr\")</pre>",
parameters = {
@Param(name = "x", doc = "The object to check."),
@Param(name = "name", 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 "
+ "<code>default</code> (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 "
+ "<code>default</code> is not given. <code>getattr(x, \"foobar\")</code> is "
+ "equivalent to <code>x.foobar</code>."
+ "<pre class=\"language-python\">getattr(ctx.attr, \"myattr\")\n"
+ "getattr(ctx.attr, \"myattr\", \"mydefault\")</pre>",
parameters = {
@Param(name = "x", doc = "The struct whose attribute is accessed."),
@Param(name = "name", doc = "The name of the struct attribute."),
@Param(
name = "default",
defaultValue = "unbound",
doc =
"The default value to return in case the struct "
+ "doesn't have an attribute of the given name."
)
},
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<String> 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 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 <code>args</code> as output. It will be prefixed with the string <code>"
+ "\"DEBUG\"</code> and the location (file and line number) of this call. It can be "
+ "used for debugging."
+ "<p>Using <code>print</code> in production code is discouraged due to the spam it "
+ "creates for users. For deprecations, prefer a hard error using <a href=\"#fail\">"
+ "fail()</a> 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(Printer::str)
.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 = "zip",
doc =
"Returns a <code>list</code> of <code>tuple</code>s, where the i-th tuple contains "
+ "the i-th element from each of the argument sequences or iterables. The list has the "
+ "size of the shortest input. With a single iterable argument, it returns a list of "
+ "1-tuples. With no arguments, it returns an empty list. Examples:"
+ "<pre class=\"language-python\">"
+ "zip() # == []\n"
+ "zip([1, 2]) # == [(1,), (2,)]\n"
+ "zip([1, 2], [3, 4]) # == [(1, 3), (2, 4)]\n"
+ "zip([1, 2], [3, 4, 5]) # == [(1, 3), (2, 4)]</pre>",
extraPositionals = @Param(name = "args", doc = "lists to zip."),
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<Tuple<?>> result = new ArrayList<>();
boolean allHasNext;
do {
allHasNext = !args.isEmpty();
List<Object> elem = Lists.newArrayListWithExpectedSize(args.size());
for (Iterator<?> iterator : iterators) {
if (iterator.hasNext()) {
elem.add(iterator.next());
} else {
allHasNext = false;
}
}
if (allHasNext) {
result.add(Tuple.copyOf(elem));
}
} while (allHasNext);
return MutableList.wrapUnsafe(env, result);
}
};
/** Skylark String module. */
@SkylarkModule(
name = "string",
category = SkylarkModuleCategory.BUILTIN,
doc =
"A language built-in type to support strings. "
+ "Examples of string literals:<br>"
+ "<pre class=\"language-python\">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</pre>"
+ "Strings are iterable and support the <code>in</code> operator. Examples:<br>"
+ "<pre class=\"language-python\">\"bc\" in \"abcd\" # evaluates to True\n"
+ "x = [s for s in \"abc\"] # x == [\"a\", \"b\", \"c\"]</pre>\n"
+ "Implicit concatenation of strings is not allowed; use the <code>+</code> "
+ "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:<br>"
+ "<pre class=\"language-python\">"
+ "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"
+ "</pre>"
)
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: "
+ "<a href=\"globals.html#True\">True</a> and "
+ "<a href=\"globals.html#False\">False</a>. "
+ "Any value can be converted to a boolean using the "
+ "<a href=\"globals.html#bool\">bool</a> function."
)
public static final class BoolModule {}
static final List<BaseFunction> 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);
}
}