blob: 7b00ea0d1e9b12ea00b9c6a18e713262113d1228 [file] [log] [blame]
// Copyright 2014 Google Inc. 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.packages;
import static com.google.devtools.build.lib.syntax.SkylarkFunction.cast;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
import com.google.devtools.build.lib.collect.nestedset.Order;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.events.Location;
import com.google.devtools.build.lib.packages.Type.ConversionException;
import com.google.devtools.build.lib.syntax.AbstractFunction;
import com.google.devtools.build.lib.syntax.AbstractFunction.NoArgFunction;
import com.google.devtools.build.lib.syntax.ClassObject;
import com.google.devtools.build.lib.syntax.ClassObject.SkylarkClassObject;
import com.google.devtools.build.lib.syntax.DotExpression;
import com.google.devtools.build.lib.syntax.Environment;
import com.google.devtools.build.lib.syntax.EvalException;
import com.google.devtools.build.lib.syntax.EvalUtils;
import com.google.devtools.build.lib.syntax.FuncallExpression;
import com.google.devtools.build.lib.syntax.Function;
import com.google.devtools.build.lib.syntax.MixedModeFunction;
import com.google.devtools.build.lib.syntax.SelectorValue;
import com.google.devtools.build.lib.syntax.SkylarkBuiltin;
import com.google.devtools.build.lib.syntax.SkylarkBuiltin.Param;
import com.google.devtools.build.lib.syntax.SkylarkEnvironment;
import com.google.devtools.build.lib.syntax.SkylarkList;
import com.google.devtools.build.lib.syntax.SkylarkModule;
import com.google.devtools.build.lib.syntax.SkylarkNestedSet;
import com.google.devtools.build.lib.syntax.SkylarkType;
import com.google.devtools.build.lib.syntax.SkylarkType.SkylarkFunctionType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.ExecutionException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* A helper class containing built in functions for the Build and the Build Extension Language.
*/
public class MethodLibrary {
private MethodLibrary() {}
// Convert string index in the same way Python does.
// If index is negative, starts from the end.
// If index is outside bounds, it is restricted to the valid range.
private static int getClampedIndex(int index, int length) {
if (index < 0) {
index += length;
}
return Math.max(Math.min(index, length), 0);
}
// Emulate Python substring function
// It converts out of range indices, and never fails
private static String getPythonSubstring(String str, int start, int end) {
start = getClampedIndex(start, str.length());
end = getClampedIndex(end, str.length());
if (start > end) {
return "";
} else {
return str.substring(start, end);
}
}
public static int getListIndex(Object key, int listSize, FuncallExpression ast)
throws ConversionException, EvalException {
// Get the nth element in the list
int index = Type.INTEGER.convert(key, "index operand");
if (index < 0) {
index += listSize;
}
if (index < 0 || index >= listSize) {
throw new EvalException(ast.getLocation(), "List index out of range (index is "
+ index + ", but list has " + listSize + " elements)");
}
return index;
}
// supported string methods
@SkylarkBuiltin(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>",
mandatoryParams = {
@Param(name = "elements", type = SkylarkList.class, doc = "The objects to join.")})
private static Function join = new MixedModeFunction("join",
ImmutableList.of("this", "elements"), 2, false) {
@Override
public Object call(Object[] args, FuncallExpression ast) throws ConversionException {
String thisString = Type.STRING.convert(args[0], "'join' operand");
List<?> seq = Type.OBJECT_LIST.convert(args[1], "'join' argument");
return Joiner.on(thisString).join(seq);
}};
@SkylarkBuiltin(name = "lower", objectType = StringModule.class, returnType = String.class,
doc = "Returns the lower case version of this string.")
private static Function lower = new MixedModeFunction("lower",
ImmutableList.of("this"), 1, false) {
@Override
public Object call(Object[] args, FuncallExpression ast) throws ConversionException {
String thiz = Type.STRING.convert(args[0], "'lower' operand");
return thiz.toLowerCase();
}
};
@SkylarkBuiltin(name = "upper", objectType = StringModule.class, returnType = String.class,
doc = "Returns the upper case version of this string.")
private static Function upper = new MixedModeFunction("upper",
ImmutableList.of("this"), 1, false) {
@Override
public Object call(Object[] args, FuncallExpression ast) throws ConversionException {
String thiz = Type.STRING.convert(args[0], "'upper' operand");
return thiz.toUpperCase();
}
};
@SkylarkBuiltin(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>.",
mandatoryParams = {
@Param(name = "old", type = String.class, doc = "The string to be replaced."),
@Param(name = "new", type = String.class, doc = "The string to replace with.")},
optionalParams = {
@Param(name = "maxsplit", type = Integer.class, doc = "The maximum number of replacements.")})
private static Function replace =
new MixedModeFunction("replace", ImmutableList.of("this", "old", "new", "maxsplit"), 3, false) {
@Override
public Object call(Object[] args, FuncallExpression ast) throws EvalException,
ConversionException {
String thiz = Type.STRING.convert(args[0], "'replace' operand");
String old = Type.STRING.convert(args[1], "'replace' argument");
String neww = Type.STRING.convert(args[2], "'replace' argument");
int maxsplit =
args[3] != null ? Type.INTEGER.convert(args[3], "'replace' argument")
: Integer.MAX_VALUE;
StringBuffer sb = new StringBuffer();
try {
Matcher m = Pattern.compile(old, Pattern.LITERAL).matcher(thiz);
for (int i = 0; i < maxsplit && m.find(); i++) {
m.appendReplacement(sb, Matcher.quoteReplacement(neww));
}
m.appendTail(sb);
} catch (IllegalStateException e) {
throw new EvalException(ast.getLocation(), e.getMessage() + " in call to replace");
}
return sb.toString();
}
};
@SkylarkBuiltin(name = "split", objectType = StringModule.class, returnType = SkylarkList.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>.",
optionalParams = {
@Param(name = "sep", type = String.class,
doc = "The string to split on, default is space (\" \")."),
@Param(name = "maxsplit", type = Integer.class, doc = "The maximum number of splits.")})
private static Function split = new MixedModeFunction("split",
ImmutableList.of("this", "sep", "maxsplit"), 1, false) {
@Override
public Object call(Object[] args, FuncallExpression ast, Environment env)
throws ConversionException {
String thiz = Type.STRING.convert(args[0], "'split' operand");
String sep = args[1] != null
? Type.STRING.convert(args[1], "'split' argument")
: " ";
int maxsplit = args[2] != null
? Type.INTEGER.convert(args[2], "'split' argument") + 1 // last is remainder
: -1;
String[] ss = Pattern.compile(sep, Pattern.LITERAL).split(thiz,
maxsplit);
List<String> result = java.util.Arrays.asList(ss);
return env.isSkylarkEnabled() ? SkylarkList.list(result, String.class) : result;
}
};
// Common implementation for find, rfind, index, rindex.
// forward is true iff we want to return the last matching index.
private static int stringFind(String functionName, boolean forward, Object[] args)
throws ConversionException {
String thiz = Type.STRING.convert(args[0], functionName + " operand");
String sub = Type.STRING.convert(args[1], functionName + " argument");
int start = 0;
if (args[2] != null) {
start = Type.INTEGER.convert(args[2], functionName + " argument");
}
int end = thiz.length();
if (args[3] != null) {
end = Type.INTEGER.convert(args[3], functionName + " argument");
}
String substr = getPythonSubstring(thiz, start, end);
int subpos = forward ? substr.indexOf(sub) : substr.lastIndexOf(sub);
start = getClampedIndex(start, thiz.length());
return subpos < 0 ? subpos : subpos + start;
}
@SkylarkBuiltin(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.",
mandatoryParams = {
@Param(name = "sub", type = String.class, doc = "The substring to find.")},
optionalParams = {
@Param(name = "start", type = Integer.class, doc = "Restrict to search from this position."),
@Param(name = "end", type = Integer.class, doc = "Restrict to search before this position.")})
private static Function rfind =
new MixedModeFunction("rfind", ImmutableList.of("this", "sub", "start", "end"), 2, false) {
@Override
public Object call(Object[] args, FuncallExpression ast) throws ConversionException {
return stringFind("rfind", false, args);
}
};
@SkylarkBuiltin(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.",
mandatoryParams = {
@Param(name = "sub", type = String.class, doc = "The substring to find.")},
optionalParams = {
@Param(name = "start", type = Integer.class, doc = "Restrict to search from this position."),
@Param(name = "end", type = Integer.class, doc = "Restrict to search before this position.")})
private static Function find =
new MixedModeFunction("find", ImmutableList.of("this", "sub", "start", "end"), 2, false) {
@Override
public Object call(Object[] args, FuncallExpression ast)
throws ConversionException {
return stringFind("find", true, args);
}
};
@SkylarkBuiltin(name = "rindex", objectType = StringModule.class, returnType = Integer.class,
doc = "Returns the last index where <code>sub</code> is found, "
+ "or throw 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.",
mandatoryParams = {
@Param(name = "sub", type = String.class, doc = "The substring to find.")},
optionalParams = {
@Param(name = "start", type = Integer.class, doc = "Restrict to search from this position."),
@Param(name = "end", type = Integer.class, doc = "Restrict to search before this position.")})
private static Function rindex =
new MixedModeFunction("rindex", ImmutableList.of("this", "sub", "start", "end"), 2, false) {
@Override
public Object call(Object[] args, FuncallExpression ast)
throws EvalException, ConversionException {
int res = stringFind("rindex", false, args);
if (res < 0) {
throw new EvalException(ast.getLocation(),
"substring '" + args[1] + "' not found in '" + args[0] + "'");
}
return res;
}
};
@SkylarkBuiltin(name = "index", objectType = StringModule.class, returnType = Integer.class,
doc = "Returns the first index where <code>sub</code> is found, "
+ "or throw 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.",
mandatoryParams = {
@Param(name = "sub", type = String.class, doc = "The substring to find.")},
optionalParams = {
@Param(name = "start", type = Integer.class, doc = "Restrict to search from this position."),
@Param(name = "end", type = Integer.class, doc = "Restrict to search before this position.")})
private static Function index =
new MixedModeFunction("index", ImmutableList.of("this", "sub", "start", "end"), 2, false) {
@Override
public Object call(Object[] args, FuncallExpression ast)
throws EvalException, ConversionException {
int res = stringFind("index", true, args);
if (res < 0) {
throw new EvalException(ast.getLocation(),
"substring '" + args[1] + "' not found in '" + args[0] + "'");
}
return res;
}
};
@SkylarkBuiltin(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.",
mandatoryParams = {
@Param(name = "sub", type = String.class, doc = "The substring to count.")},
optionalParams = {
@Param(name = "start", type = Integer.class, doc = "Restrict to search from this position."),
@Param(name = "end", type = Integer.class, doc = "Restrict to search before this position.")})
private static Function count =
new MixedModeFunction("count", ImmutableList.of("this", "sub", "start", "end"), 2, false) {
@Override
public Object call(Object[] args, FuncallExpression ast)
throws ConversionException {
String thiz = Type.STRING.convert(args[0], "'count' operand");
String sub = Type.STRING.convert(args[1], "'count' argument");
int start = 0;
if (args[2] != null) {
start = Type.INTEGER.convert(args[2], "'count' argument");
}
int end = thiz.length();
if (args[3] != null) {
end = Type.INTEGER.convert(args[3], "'count' argument");
}
String str = getPythonSubstring(thiz, start, end);
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;
}
};
@SkylarkBuiltin(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.",
mandatoryParams = {
@Param(name = "sub", type = String.class, doc = "The substring to check.")},
optionalParams = {
@Param(name = "start", type = Integer.class, doc = "Test beginning at this position."),
@Param(name = "end", type = Integer.class, doc = "Stop comparing at this position.")})
private static Function endswith =
new MixedModeFunction("endswith", ImmutableList.of("this", "sub", "start", "end"), 2, false) {
@Override
public Object call(Object[] args, FuncallExpression ast)
throws ConversionException {
String thiz = Type.STRING.convert(args[0], "'endswith' operand");
String sub = Type.STRING.convert(args[1], "'endswith' argument");
int start = 0;
if (args[2] != null) {
start = Type.INTEGER.convert(args[2], "'endswith' argument");
}
int end = thiz.length();
if (args[3] != null) {
end = Type.INTEGER.convert(args[3], "");
}
return getPythonSubstring(thiz, start, end).endsWith(sub);
}
};
@SkylarkBuiltin(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.",
mandatoryParams = {
@Param(name = "sub", type = String.class, doc = "The substring to check.")},
optionalParams = {
@Param(name = "start", type = Integer.class, doc = "Test beginning at this position."),
@Param(name = "end", type = Integer.class, doc = "Stop comparing at this position.")})
private static Function startswith =
new MixedModeFunction("startswith", ImmutableList.of("this", "sub", "start", "end"), 2, false) {
@Override
public Object call(Object[] args, FuncallExpression ast) throws ConversionException {
String thiz = Type.STRING.convert(args[0], "'startswith' operand");
String sub = Type.STRING.convert(args[1], "'startswith' argument");
int start = 0;
if (args[2] != null) {
start = Type.INTEGER.convert(args[2], "'startswith' argument");
}
int end = thiz.length();
if (args[3] != null) {
end = Type.INTEGER.convert(args[3], "'startswith' argument");
}
return getPythonSubstring(thiz, start, end).startsWith(sub);
}
};
// TODO(bazel-team): Maybe support an argument to tell the type of the whitespace.
@SkylarkBuiltin(name = "strip", objectType = StringModule.class, returnType = String.class,
doc = "Returns a copy of the string in which all whitespace characters "
+ "have been stripped from the beginning and the end of the string.")
private static Function strip =
new MixedModeFunction("strip", ImmutableList.of("this"), 1, false) {
@Override
public Object call(Object[] args, FuncallExpression ast)
throws ConversionException {
String operand = Type.STRING.convert(args[0], "'strip' operand");
return operand.trim();
}
};
// slice operator
@SkylarkBuiltin(name = "$slice", hidden = true,
doc = "x[<code>start</code>:<code>end</code>] returns a slice or a list slice.")
private static Function slice = new MixedModeFunction("$slice",
ImmutableList.of("this", "start", "end"), 3, false) {
@Override
public Object call(Object[] args, FuncallExpression ast, Environment env)
throws EvalException, ConversionException {
int left = Type.INTEGER.convert(args[1], "start operand");
int right = Type.INTEGER.convert(args[2], "end operand");
// Substring
if (args[0] instanceof String) {
String thiz = Type.STRING.convert(args[0], "substring operand");
return getPythonSubstring(thiz, left, right);
}
// List slice
List<Object> list = Type.OBJECT_LIST.convert(args[0], "list operand");
left = getClampedIndex(left, list.size());
right = getClampedIndex(right, list.size());
List<Object> result = Lists.newArrayList();
for (int i = left; i < right; i++) {
result.add(list.get(i));
}
return convert(result, env, ast.getLocation());
}
};
// supported list methods
@SkylarkBuiltin(name = "append", hidden = true,
doc = "Adds an item to the end of the list.")
private static Function append = new MixedModeFunction("append",
ImmutableList.of("this", "x"), 2, false) {
@Override
public Object call(Object[] args, FuncallExpression ast) throws EvalException,
ConversionException {
List<Object> thiz = Type.OBJECT_LIST.convert(args[0], "'append' operand");
thiz.add(args[1]);
return Environment.NONE;
}
};
@SkylarkBuiltin(name = "extend", hidden = true,
doc = "Adds all items to the end of the list.")
private static Function extend = new MixedModeFunction("extend",
ImmutableList.of("this", "x"), 2, false) {
@Override
public Object call(Object[] args, FuncallExpression ast) throws EvalException,
ConversionException {
List<Object> thiz = Type.OBJECT_LIST.convert(args[0], "'extend' operand");
List<Object> l = Type.OBJECT_LIST.convert(args[1], "'extend' argument");
thiz.addAll(l);
return Environment.NONE;
}
};
// dictionary access operator
@SkylarkBuiltin(name = "$index", hidden = true,
doc = "Returns the nth element of a list or string, "
+ "or looks up a value in a dictionary.")
private static Function indexOperator = new MixedModeFunction("$index",
ImmutableList.of("this", "index"), 2, false) {
@Override
public Object call(Object[] args, FuncallExpression ast) throws EvalException,
ConversionException {
Object collectionCandidate = args[0];
Object key = args[1];
if (collectionCandidate instanceof Map<?, ?>) {
Map<?, ?> dictionary = (Map<?, ?>) collectionCandidate;
if (!dictionary.containsKey(key)) {
throw new EvalException(ast.getLocation(), "Key '" + key + "' not found in dictionary");
}
return dictionary.get(key);
} else if (collectionCandidate instanceof List<?>) {
List<Object> list = Type.OBJECT_LIST.convert(collectionCandidate, "index operand");
if (!list.isEmpty()) {
int index = getListIndex(key, list.size(), ast);
return list.get(index);
}
throw new EvalException(ast.getLocation(), "List is empty");
} else if (collectionCandidate instanceof SkylarkList) {
SkylarkList list = (SkylarkList) collectionCandidate;
if (!list.isEmpty()) {
int index = getListIndex(key, list.size(), ast);
return list.get(index);
}
throw new EvalException(ast.getLocation(), "List is empty");
} else if (collectionCandidate instanceof String) {
String str = (String) collectionCandidate;
int index = getListIndex(key, str.length(), ast);
return str.substring(index, index + 1);
} else {
// TODO(bazel-team): This is dead code, get rid of it.
throw new EvalException(ast.getLocation(), String.format(
"Unsupported datatype (%s) for indexing, only works for dict and list",
EvalUtils.getDataTypeName(collectionCandidate)));
}
}
};
@SkylarkBuiltin(name = "values", objectType = DictModule.class, returnType = SkylarkList.class,
doc = "Return the list of values. Dictionaries are always sorted by their keys:"
+ "<pre class=\"language-python\">"
+ "{2: \"a\", 4: \"b\", 1: \"c\"}.values() == [\"c\", \"a\", \"b\"]</pre>\n")
private static Function values = new NoArgFunction("values") {
@Override
public Object call(Object self, FuncallExpression ast, Environment env)
throws EvalException, InterruptedException {
// Use a TreeMap to ensure consistent ordering.
Map<?, ?> dict = new TreeMap<>((Map<?, ?>) self);
return convert(dict.values(), env, ast.getLocation());
}
};
@SkylarkBuiltin(name = "items", objectType = DictModule.class, returnType = SkylarkList.class,
doc = "Return the list of key-value tuples. Dictionaries are always sorted by their keys:"
+ "<pre class=\"language-python\">"
+ "{2: \"a\", 4: \"b\", 1: \"c\"}.items() == [(1, \"c\"), (2, \"a\"), (4, \"b\")]"
+ "</pre>\n")
private static Function items = new NoArgFunction("items") {
@Override
public Object call(Object self, FuncallExpression ast, Environment env)
throws EvalException, InterruptedException {
// Use a TreeMap to ensure consistent ordering.
Map<?, ?> dict = new TreeMap<>((Map<?, ?>) self);
List<Object> list = Lists.newArrayListWithCapacity(dict.size());
for (Map.Entry<?, ?> entries : dict.entrySet()) {
List<?> item = ImmutableList.of(entries.getKey(), entries.getValue());
list.add(env.isSkylarkEnabled() ? SkylarkList.tuple(item) : item);
}
return convert(list, env, ast.getLocation());
}
};
@SkylarkBuiltin(name = "keys", objectType = DictModule.class, returnType = SkylarkList.class,
doc = "Return the list of keys. Dictionaries are always sorted by their keys:"
+ "<pre class=\"language-python\">{2: \"a\", 4: \"b\", 1: \"c\"}.keys() == [1, 2, 4]"
+ "</pre>\n")
private static Function keys = new NoArgFunction("keys") {
@Override
public Object call(Object self, FuncallExpression ast, Environment env)
throws EvalException, InterruptedException {
Map<Comparable<?>, Object> dict = (Map<Comparable<?>, Object>) self;
return convert(Ordering.natural().sortedCopy(dict.keySet()), env, ast.getLocation());
}
};
@SkylarkBuiltin(name = "get", objectType = DictModule.class,
doc = "Return 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.",
mandatoryParams = {
@Param(name = "key", doc = "The key to look for.")},
optionalParams = {
@Param(name = "default",
doc = "The default value to use (instead of None) if the key is not found.")})
private static Function get =
new MixedModeFunction("get", ImmutableList.of("this", "key", "default"), 2, false) {
@Override
public Object call(Object[] args, FuncallExpression ast) throws ConversionException {
Map<?, ?> dict = (Map<?, ?>) args[0];
Object key = args[1];
if (dict.containsKey(key)) {
return dict.get(key);
}
if (args[2] == null) {
return Environment.NONE;
}
return args[2];
}
};
// TODO(bazel-team): Use the same type for both Skylark and BUILD files.
@SuppressWarnings("unchecked")
private static Iterable<Object> convert(Collection<?> list, Environment env, Location loc)
throws EvalException {
if (env.isSkylarkEnabled()) {
return SkylarkList.list(list, loc);
} else {
return Lists.newArrayList(list);
}
}
// unary minus
@SkylarkBuiltin(name = "-", hidden = true, doc = "Unary minus operator.")
private static Function minus = new MixedModeFunction("-", ImmutableList.of("this"), 1, false) {
@Override
public Object call(Object[] args, FuncallExpression ast) throws ConversionException {
int num = Type.INTEGER.convert(args[0], "'unary minus' argument");
return -num;
}
};
@SkylarkBuiltin(name = "list", returnType = SkylarkList.class,
doc = "Converts a collection (e.g. set or dictionary) to a list."
+ "<pre class=\"language-python\">list([1, 2]) == [1, 2]\n"
+ "list(set([2, 3, 2])) == [2, 3]\n"
+ "list({5: \"a\", 2: \"b\", 4: \"c\"}) == [2, 4, 5]</pre>",
mandatoryParams = {@Param(name = "x", doc = "The object to convert.")})
private static Function list = new MixedModeFunction("list",
ImmutableList.of("list"), 1, false) {
@Override
public Object call(Object[] args, FuncallExpression ast) throws EvalException {
Location loc = ast.getLocation();
return SkylarkList.list(EvalUtils.toCollection(args[0], loc), loc);
}
};
@SkylarkBuiltin(name = "len", returnType = Integer.class, doc =
"Returns the length of a string, list, tuple, set, or dictionary.",
mandatoryParams = {@Param(name = "x", doc = "The object to check length of.")})
private static Function len = new MixedModeFunction("len",
ImmutableList.of("list"), 1, false) {
@Override
public Object call(Object[] args, FuncallExpression ast) throws EvalException {
Object arg = args[0];
int l = EvalUtils.size(arg);
if (l == -1) {
throw new EvalException(ast.getLocation(),
EvalUtils.getDataTypeName(arg) + " is not iterable");
}
return l;
}
};
@SkylarkBuiltin(name = "str", returnType = String.class, doc =
"Converts any object to string. This is useful for debugging.",
mandatoryParams = {@Param(name = "x", doc = "The object to convert.")})
private static Function str = new MixedModeFunction("str", ImmutableList.of("this"), 1, false) {
@Override
public Object call(Object[] args, FuncallExpression ast) throws EvalException {
return EvalUtils.printValue(args[0]);
}
};
@SkylarkBuiltin(name = "bool", returnType = Boolean.class, doc = "Converts an object to boolean. "
+ "It returns False if the object is None, False, an empty string, the number 0, or an "
+ "empty collection. Otherwise, it returns True. Similarly to Python <code>bool</code> "
+ "is also a type.",
mandatoryParams = {@Param(name = "x", doc = "The variable to convert.")})
private static Function bool = new MixedModeFunction("bool",
ImmutableList.of("this"), 1, false) {
@Override
public Object call(Object[] args, FuncallExpression ast) throws EvalException {
return EvalUtils.toBoolean(args[0]);
}
};
@SkylarkBuiltin(name = "int", returnType = Integer.class, doc = "Converts a string to int, "
+ "using base 10. It raises an error if the conversion fails."
+ "<pre class=\"language-python\">int(\"123\") == 123</pre>",
mandatoryParams = {@Param(name = "x", type = String.class, doc = "The string to convert.")})
private static Function int_ =
new MixedModeFunction("int", ImmutableList.of("x"), 1, false) {
@Override
public Object call(Object[] args, FuncallExpression ast)
throws EvalException, ConversionException {
String str = Type.STRING.convert(args[0], "'int' operand");
try {
return Integer.parseInt(str);
} catch (NumberFormatException e) {
throw new EvalException(ast.getLocation(), "invalid literal for int(): " + str);
}
}
};
@SkylarkBuiltin(name = "struct", returnType = SkylarkClassObject.class, doc =
"Creates an immutable struct using the keyword arguments as fields. It is used to group "
+ "multiple values together.Example:<br>"
+ "<pre class=\"language-python\">s = struct(x = 2, y = 3)\n"
+ "return s.x + s.y # returns 5</pre>")
private static Function struct = new AbstractFunction("struct") {
@Override
public Object call(List<Object> args, Map<String, Object> kwargs, FuncallExpression ast,
Environment env) throws EvalException, InterruptedException {
if (!args.isEmpty()) {
throw new EvalException(ast.getLocation(), "struct only supports keyword arguments");
}
return new SkylarkClassObject(kwargs, ast.getLocation());
}
};
@SkylarkBuiltin(name = "set", returnType = SkylarkNestedSet.class,
doc = "Creates a set from the <code>items</code>. The set supports nesting other sets of the"
+ " same element type in it. For this reason sets are also referred to as <i>nested sets</i>"
+ " (all Skylark sets are nested sets). A desired iteration order can also be specified.<br>"
+ " Examples:<br><pre class=\"language-python\">set([1, set([2, 3]), 2])\n"
+ "set([1, 2, 3], order=\"compile\")</pre>",
optionalParams = {
@Param(name = "items", type = SkylarkList.class,
doc = "The items to initialize the set with. May contain both standalone items and other"
+ " sets."),
@Param(name = "order", type = String.class,
doc = "The ordering strategy for the set if it's nested, "
+ "possible values are: <code>stable</code> (default), <code>compile</code>, "
+ "<code>link</code> or <code>naive_link</code>.")})
private static final Function set =
new MixedModeFunction("set", ImmutableList.of("items", "order"), 0, false) {
@Override
public Object call(Object[] args, FuncallExpression ast) throws EvalException,
ConversionException {
Order order = SkylarkNestedSet.parseOrder((String) args[1], ast.getLocation());
if (args[0] == null) {
return new SkylarkNestedSet(order, SkylarkList.EMPTY_LIST, ast.getLocation());
}
return new SkylarkNestedSet(order, args[0], ast.getLocation());
}
};
@SkylarkBuiltin(name = "enumerate", returnType = SkylarkList.class,
doc = "Return a list of pairs (two-element lists), 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",
mandatoryParams = {
@Param(name = "list", type = SkylarkList.class,
doc = "input list"),
})
private static Function enumerate = new MixedModeFunction("enumerate",
ImmutableList.of("list"), 1, false) {
@Override
public Object call(Object[] args, FuncallExpression ast) throws EvalException,
ConversionException {
// Note that enumerate is only available in Skylark.
SkylarkList input = cast(
args[0], SkylarkList.class, "enumerate operand", ast.getLocation());
List<SkylarkList> result = Lists.newArrayList();
int count = 0;
for (Object obj : input) {
result.add(SkylarkList.tuple(Lists.newArrayList(count, obj)));
count++;
}
return SkylarkList.list(result, ast.getLocation());
}
};
@SkylarkBuiltin(name = "range", returnType = SkylarkList.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>",
mandatoryParams = {
@Param(name = "start", type = Integer.class,
doc = "Value of the first element"),
},
optionalParams = {
@Param(name = "stop", type = Integer.class,
doc = "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,
doc = "The increment (default is 1). It may be negative.")})
private static final Function range =
new MixedModeFunction("range", ImmutableList.of("start", "stop", "step"), 1, false) {
@Override
public Object call(Object[] args, FuncallExpression ast) throws EvalException,
ConversionException {
int start;
int stop;
if (args[1] == null) {
start = 0;
stop = Type.INTEGER.convert(args[0], "stop");
} else {
start = Type.INTEGER.convert(args[0], "start");
stop = Type.INTEGER.convert(args[1], "stop");
}
int step = args[2] == null ? 1 : Type.INTEGER.convert(args[2], "step");
if (step == 0) {
throw new EvalException(ast.getLocation(), "step cannot be 0");
}
List<Integer> result = Lists.newArrayList();
if (step > 0) {
while (start < stop) {
result.add(start);
start += step;
}
} else {
while (start > stop) {
result.add(start);
start += step;
}
}
return SkylarkList.list(result, Integer.class);
}
};
/**
* Returns a function-value implementing "select" (i.e. configurable attributes)
* in the specified package context.
*/
@SkylarkBuiltin(name = "select",
doc = "Creates a SelectorValue from the dict parameter.",
mandatoryParams = {@Param(name = "x", type = Map.class, doc = "The parameter to convert.")})
private static final Function select = new MixedModeFunction("select",
ImmutableList.of("x"), 1, false) {
@Override
public Object call(Object[] args, FuncallExpression ast)
throws EvalException, ConversionException {
Object dict = args[0];
if (!(dict instanceof Map<?, ?>)) {
throw new EvalException(ast.getLocation(),
"select({...}) argument isn't a dictionary");
}
return new SelectorValue((Map<?, ?>) dict);
}
};
/**
* Returns true if the object has a field of the given name, otherwise false.
*/
@SkylarkBuiltin(name = "hasattr", returnType = Boolean.class,
doc = "Returns True if the object <code>x</code> has a field of the given <code>name</code>, "
+ "otherwise False. Example:<br>"
+ "<pre class=\"language-python\">hasattr(ctx.attr, \"myattr\")</pre>",
mandatoryParams = {
@Param(name = "object", doc = "The object to check."),
@Param(name = "name", type = String.class, doc = "The name of the field.")})
private static final Function hasattr =
new MixedModeFunction("hasattr", ImmutableList.of("object", "name"), 2, false) {
@Override
public Object call(Object[] args, FuncallExpression ast, Environment env)
throws EvalException, ConversionException {
Object obj = args[0];
String name = cast(args[1], String.class, "name", ast.getLocation());
if (obj instanceof ClassObject && ((ClassObject) obj).getValue(name) != null) {
return true;
}
if (env.getFunctionNames(obj.getClass()).contains(name)) {
return true;
}
try {
return FuncallExpression.getMethodNames(obj.getClass()).contains(name);
} catch (ExecutionException e) {
// This shouldn't happen
throw new EvalException(ast.getLocation(), e.getMessage());
}
}
};
@SkylarkBuiltin(name = "getattr",
doc = "Returns the struct's field of the given name if exists, otherwise <code>default</code>"
+ " if specified, otherwise rasies an error. For example, <code>getattr(x, \"foobar\")"
+ "</code> is equivalent to <code>x.foobar</code>."
+ "Example:<br>"
+ "<pre class=\"language-python\">getattr(ctx.attr, \"myattr\")\n"
+ "getattr(ctx.attr, \"myattr\", \"mydefault\")</pre>",
mandatoryParams = {
@Param(name = "object", doc = "The struct which's field is accessed."),
@Param(name = "name", doc = "The name of the struct field.")},
optionalParams = {
@Param(name = "default", doc = "The default value to return in case the struct "
+ "doesn't have a field of the given name.")})
private static final Function getattr = new MixedModeFunction(
"getattr", ImmutableList.of("object", "name", "default"), 2, false) {
@Override
public Object call(Object[] args, FuncallExpression ast, Environment env)
throws EvalException {
Object obj = args[0];
String name = cast(args[1], String.class, "name", ast.getLocation());
Object result = DotExpression.eval(obj, name, ast.getLocation());
if (result == null) {
if (args[2] != null) {
return args[2];
} else {
throw new EvalException(ast.getLocation(), "Object of type '"
+ EvalUtils.getDataTypeName(obj) + "' has no field '" + name + "'");
}
}
return result;
}
};
@SkylarkBuiltin(name = "dir", returnType = SkylarkList.class,
doc = "Returns a list strings: the names of the fields and "
+ "methods of the parameter object.",
mandatoryParams = {@Param(name = "object", doc = "The object to check.")})
private static final Function dir = new MixedModeFunction(
"dir", ImmutableList.of("object"), 1, false) {
@Override
public Object call(Object[] args, FuncallExpression ast, Environment env)
throws EvalException {
Object obj = args[0];
// Order the fields alphabetically.
Set<String> fields = new TreeSet<>();
if (obj instanceof ClassObject) {
fields.addAll(((ClassObject) obj).getKeys());
}
fields.addAll(env.getFunctionNames(obj.getClass()));
try {
fields.addAll(FuncallExpression.getMethodNames(obj.getClass()));
} catch (ExecutionException e) {
// This shouldn't happen
throw new EvalException(ast.getLocation(), e.getMessage());
}
return SkylarkList.list(fields, String.class);
}
};
@SkylarkBuiltin(name = "type", returnType = String.class,
doc = "Returns the type name of its argument.",
mandatoryParams = {@Param(name = "object", doc = "The object to check type of.")})
private static final Function type = new MixedModeFunction("type",
ImmutableList.of("object"), 1, false) {
@Override
public Object call(Object[] args, FuncallExpression ast) throws EvalException {
// There is no 'type' type in Skylark, so we return a string with the type name.
return EvalUtils.getDataTypeName(args[0], false);
}
};
@SkylarkBuiltin(name = "fail",
doc = "Raises an error (the execution stops), except if the <code>when</code> condition "
+ "is False.",
returnType = Environment.NoneType.class,
mandatoryParams = {
@Param(name = "msg", type = String.class, doc = "Error message to display for the user")},
optionalParams = {
@Param(name = "attr", type = String.class,
doc = "The name of the attribute that caused the error"),
@Param(name = "when", type = Boolean.class,
doc = "When False, the function does nothing. Default is True.")})
private static final Function fail = new MixedModeFunction(
"fail", ImmutableList.of("msg", "attr", "when"), 1, false) {
@Override
public Object call(Object[] args, FuncallExpression ast, Environment env)
throws EvalException {
if (args[2] != null) {
if (!EvalUtils.toBoolean(args[2])) {
return Environment.NONE;
}
}
String msg = cast(args[0], String.class, "msg", ast.getLocation());
if (args[1] != null) {
msg = "attribute " + cast(args[1], String.class, "attr", ast.getLocation())
+ ": " + msg;
}
throw new EvalException(ast.getLocation(), msg);
}
};
@SkylarkBuiltin(name = "print", returnType = Environment.NoneType.class,
doc = "Prints <code>msg</code> to the console.",
mandatoryParams = {
@Param(name = "*args", doc = "The objects to print.")},
optionalParams = {
@Param(name = "sep", type = String.class,
doc = "The separator string between the objects, default is space (\" \").")})
private static final Function print = new AbstractFunction("print") {
@Override
public Object call(List<Object> args, Map<String, Object> kwargs, FuncallExpression ast,
Environment env) throws EvalException, InterruptedException {
String sep = " ";
int count = 0;
if (kwargs.containsKey("sep")) {
sep = cast(kwargs.get("sep"), String.class, "sep", ast.getLocation());
count = 1;
}
if (kwargs.size() > count) {
kwargs = new HashMap<>(kwargs);
kwargs.remove("sep");
List<String> bad = Ordering.natural().sortedCopy(kwargs.keySet());
throw new EvalException(ast.getLocation(), "unexpected keywords: '" + bad + "'");
}
String msg = Joiner.on(sep).join(Iterables.transform(args,
new com.google.common.base.Function<Object, String>() {
@Override
public String apply(Object input) {
return EvalUtils.printValue(input);
}
}));
((SkylarkEnvironment) env).handleEvent(Event.warn(ast.getLocation(), msg));
return Environment.NONE;
}
};
@SkylarkBuiltin(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>",
returnType = SkylarkList.class)
private static final Function zip = new AbstractFunction("zip") {
@Override
public Object call(List<Object> args, Map<String, Object> kwargs, FuncallExpression ast,
Environment env) throws EvalException, InterruptedException {
Iterator<?>[] iterators = new Iterator<?>[args.size()];
for (int i = 0; i < args.size(); i++) {
iterators[i] = EvalUtils.toIterable(args.get(i), ast.getLocation()).iterator();
}
List<SkylarkList> result = new ArrayList<SkylarkList>();
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(SkylarkList.tuple(elem));
}
} while (allHasNext);
return SkylarkList.list(result, ast.getLocation());
}
};
/**
* Skylark String module.
*/
@SkylarkModule(name = "string", 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\"</pre>"
+ "Strings are iterable and support the <code>in</code> operator. Examples:<br>"
+ "<pre class=\"language-python\">\"a\" in \"abc\" # evaluates as True\n"
+ "x = []\n"
+ "for s in \"abc\":\n"
+ " x += [s] # x == [\"a\", \"b\", \"c\"]</pre>")
public static final class StringModule {}
/**
* Skylark Dict module.
*/
@SkylarkModule(name = "dict", doc =
"A language built-in type to support dicts. "
+ "Example of dict literal:<br>"
+ "<pre class=\"language-python\">d = {\"a\": 2, \"b\": 5}</pre>"
+ "Use brackets to access elements:<br>"
+ "<pre class=\"language-python\">e = d[\"a\"] # e == 2</pre>"
+ "Dicts support the <code>+</code> operator to concatenate two dicts. In case of multiple "
+ "keys the second one overrides the first one. Examples:<br>"
+ "<pre class=\"language-python\">"
+ "d = {\"a\" : 1} + {\"b\" : 2} # d == {\"a\" : 1, \"b\" : 2}\n"
+ "d += {\"c\" : 3} # d == {\"a\" : 1, \"b\" : 2, \"c\" : 3}\n"
+ "d = d + {\"c\" : 5} # d == {\"a\" : 1, \"b\" : 2, \"c\" : 5}</pre>"
+ "Since the language doesn't have mutable objects <code>d[\"a\"] = 5</code> automatically "
+ "translates to <code>d = d + {\"a\" : 5}</code>.<br>"
+ "Iterating on a dict is equivalent to iterating on its keys (in sorted order).<br>"
+ "Dicts support the <code>in</code> operator, testing membership in the keyset of the dict. "
+ "Example:<br>"
+ "<pre class=\"language-python\">\"a\" in {\"a\" : 2, \"b\" : 5} # evaluates as True"
+ "</pre>")
public static final class DictModule {}
public static final Map<Function, SkylarkType> stringFunctions = ImmutableMap
.<Function, SkylarkType>builder()
.put(join, SkylarkType.STRING)
.put(lower, SkylarkType.STRING)
.put(upper, SkylarkType.STRING)
.put(replace, SkylarkType.STRING)
.put(split, SkylarkType.of(List.class, String.class))
.put(rfind, SkylarkType.INT)
.put(find, SkylarkType.INT)
.put(rindex, SkylarkType.INT)
.put(index, SkylarkType.INT)
.put(endswith, SkylarkType.BOOL)
.put(startswith, SkylarkType.BOOL)
.put(strip, SkylarkType.STRING)
.put(slice, SkylarkType.STRING)
.put(count, SkylarkType.INT)
.build();
public static final Map<Function, SkylarkType> listPureFunctions = ImmutableMap
.<Function, SkylarkType>builder()
.put(slice, SkylarkType.LIST)
.build();
public static final List<Function> listFunctions = ImmutableList.of(append, extend);
public static final Map<Function, SkylarkType> dictFunctions = ImmutableMap
.<Function, SkylarkType>builder()
.put(items, SkylarkType.of(List.class))
.put(get, SkylarkType.UNKNOWN)
.put(keys, SkylarkType.of(Set.class))
.put(values, SkylarkType.of(List.class))
.build();
private static final Map<Function, SkylarkType> pureGlobalFunctions = ImmutableMap
.<Function, SkylarkType>builder()
// TODO(bazel-team): String methods are added two times, because there are
// a lot of cases when they are used as global functions in the depot. Those
// should be cleaned up first.
.put(bool, SkylarkType.BOOL)
.put(int_, SkylarkType.INT)
.put(len, SkylarkType.INT)
.put(minus, SkylarkType.INT)
.put(select, SkylarkType.of(SelectorValue.class))
.put(str, SkylarkType.STRING)
.build();
private static final Map<Function, SkylarkType> skylarkGlobalFunctions = ImmutableMap
.<Function, SkylarkType>builder()
.putAll(pureGlobalFunctions)
.put(list, SkylarkType.of(SkylarkList.class))
.put(struct, SkylarkType.of(ClassObject.SkylarkClassObject.class))
.put(hasattr, SkylarkType.BOOL)
.put(getattr, SkylarkType.UNKNOWN)
.put(set, SkylarkType.of(SkylarkNestedSet.class))
.put(dir, SkylarkType.of(SkylarkList.class, String.class))
.put(enumerate, SkylarkType.of(SkylarkList.class))
.put(range, SkylarkType.of(SkylarkList.class, Integer.class))
.put(type, SkylarkType.of(String.class))
.put(fail, SkylarkType.NONE)
.put(print, SkylarkType.NONE)
.put(zip, SkylarkType.LIST)
.build();
/**
* Set up a given environment for supported class methods.
*/
public static void setupMethodEnvironment(Environment env) {
env.registerFunction(Map.class, indexOperator.getName(), indexOperator);
setupMethodEnvironment(env, Map.class, dictFunctions.keySet());
env.registerFunction(String.class, indexOperator.getName(), indexOperator);
setupMethodEnvironment(env, String.class, stringFunctions.keySet());
setupMethodEnvironment(env, List.class, listPureFunctions.keySet());
setupMethodEnvironment(env, SkylarkList.class, listPureFunctions.keySet());
if (env.isSkylarkEnabled()) {
env.registerFunction(SkylarkList.class, indexOperator.getName(), indexOperator);
setupMethodEnvironment(env, skylarkGlobalFunctions.keySet());
} else {
env.registerFunction(List.class, indexOperator.getName(), indexOperator);
env.registerFunction(ImmutableList.class, indexOperator.getName(), indexOperator);
// TODO(bazel-team): listFunctions are not allowed in Skylark extensions (use += instead).
// It is allowed in BUILD files only for backward-compatibility.
setupMethodEnvironment(env, List.class, listFunctions);
setupMethodEnvironment(env, stringFunctions.keySet());
setupMethodEnvironment(env, pureGlobalFunctions.keySet());
}
}
private static void setupMethodEnvironment(
Environment env, Class<?> nameSpace, Iterable<Function> functions) {
for (Function function : functions) {
env.registerFunction(nameSpace, function.getName(), function);
}
}
private static void setupMethodEnvironment(Environment env, Iterable<Function> functions) {
for (Function function : functions) {
env.update(function.getName(), function);
}
}
private static void setupValidationEnvironment(
Map<Function, SkylarkType> functions, Map<String, SkylarkType> result) {
for (Map.Entry<Function, SkylarkType> function : functions.entrySet()) {
String name = function.getKey().getName();
result.put(name, SkylarkFunctionType.of(name, function.getValue()));
}
}
public static void setupValidationEnvironment(
Map<SkylarkType, Map<String, SkylarkType>> builtIn) {
Map<String, SkylarkType> global = builtIn.get(SkylarkType.GLOBAL);
setupValidationEnvironment(skylarkGlobalFunctions, global);
Map<String, SkylarkType> dict = new HashMap<>();
setupValidationEnvironment(dictFunctions, dict);
builtIn.put(SkylarkType.of(Map.class), dict);
Map<String, SkylarkType> string = new HashMap<>();
setupValidationEnvironment(stringFunctions, string);
builtIn.put(SkylarkType.STRING, string);
Map<String, SkylarkType> list = new HashMap<>();
setupValidationEnvironment(listPureFunctions, list);
builtIn.put(SkylarkType.LIST, list);
}
}