blob: 8de762c0beb41897c6767d832408442944f1b62d [file] [log] [blame]
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001// Copyright 2014 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package com.google.devtools.build.lib.packages;
16
Francois-Rene Rideau95b0d0c2015-04-22 16:52:13 +000017import com.google.common.base.Function;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010018import com.google.common.base.Joiner;
19import com.google.common.collect.ImmutableList;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010020import com.google.common.collect.Iterables;
21import com.google.common.collect.Lists;
Francois-Rene Rideauc673a822015-03-02 19:52:39 +000022import com.google.common.collect.Ordering;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010023import com.google.devtools.build.lib.events.Event;
24import com.google.devtools.build.lib.events.Location;
25import com.google.devtools.build.lib.packages.Type.ConversionException;
Francois-Rene Rideau95b0d0c2015-04-22 16:52:13 +000026import com.google.devtools.build.lib.syntax.BaseFunction;
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +000027import com.google.devtools.build.lib.syntax.BuiltinFunction;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010028import com.google.devtools.build.lib.syntax.ClassObject;
29import com.google.devtools.build.lib.syntax.ClassObject.SkylarkClassObject;
30import com.google.devtools.build.lib.syntax.DotExpression;
31import com.google.devtools.build.lib.syntax.Environment;
32import com.google.devtools.build.lib.syntax.EvalException;
33import com.google.devtools.build.lib.syntax.EvalUtils;
34import com.google.devtools.build.lib.syntax.FuncallExpression;
Greg Estrena4fc8772015-04-13 19:58:23 +000035import com.google.devtools.build.lib.syntax.SelectorList;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010036import com.google.devtools.build.lib.syntax.SelectorValue;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010037import com.google.devtools.build.lib.syntax.SkylarkEnvironment;
38import com.google.devtools.build.lib.syntax.SkylarkList;
39import com.google.devtools.build.lib.syntax.SkylarkModule;
40import com.google.devtools.build.lib.syntax.SkylarkNestedSet;
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +000041import com.google.devtools.build.lib.syntax.SkylarkSignature;
42import com.google.devtools.build.lib.syntax.SkylarkSignature.Param;
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +000043import com.google.devtools.build.lib.syntax.SkylarkSignatureProcessor;
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +000044import com.google.devtools.build.lib.syntax.SkylarkSignatureProcessor.HackHackEitherList;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010045
Googlerc60ec8c2015-03-23 14:20:18 +000046import java.util.ArrayList;
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +000047import java.util.Arrays;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010048import java.util.Collection;
Googlerc60ec8c2015-03-23 14:20:18 +000049import java.util.Iterator;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010050import java.util.List;
51import java.util.Map;
52import java.util.Set;
Laurent Le Brun196c1a72015-03-18 13:03:04 +000053import java.util.TreeMap;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010054import java.util.TreeSet;
55import java.util.concurrent.ExecutionException;
56import java.util.regex.Matcher;
57import java.util.regex.Pattern;
58
59/**
60 * A helper class containing built in functions for the Build and the Build Extension Language.
61 */
62public class MethodLibrary {
63
64 private MethodLibrary() {}
65
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +000066 // TODO(bazel-team):
67 // the Build language and Skylark currently have different list types:
68 // the Build language uses plain java List (usually ArrayList) which is mutable and accepts
69 // any argument, whereas Skylark uses SkylarkList which is immutable and accepts only
70 // arguments of the same kind. Some methods below use HackHackEitherList as a magic marker
71 // to indicate that either kind of lists is used depending on the evaluation context.
72 // It might be a good idea to either have separate methods for the two languages where it matters,
73 // or to unify the two languages so they use the same data structure (which might require
74 // updating all existing clients).
75
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010076 // Convert string index in the same way Python does.
77 // If index is negative, starts from the end.
78 // If index is outside bounds, it is restricted to the valid range.
Francois-Rene Rideau76023b92015-04-17 15:31:59 +000079 private static int clampIndex(int index, int length) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010080 if (index < 0) {
Laurent Le Bruneeef30f2015-03-16 15:12:35 +000081 index += length;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010082 }
Laurent Le Bruneeef30f2015-03-16 15:12:35 +000083 return Math.max(Math.min(index, length), 0);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010084 }
85
86 // Emulate Python substring function
87 // It converts out of range indices, and never fails
Francois-Rene Rideau76023b92015-04-17 15:31:59 +000088 private static String pythonSubstring(String str, int start, Object end, String msg)
89 throws ConversionException {
90 if (start == 0 && EvalUtils.isNullOrNone(end)) {
91 return str;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010092 }
Francois-Rene Rideau76023b92015-04-17 15:31:59 +000093 start = clampIndex(start, str.length());
94 int stop;
95 if (EvalUtils.isNullOrNone(end)) {
96 stop = str.length();
97 } else {
98 stop = clampIndex(Type.INTEGER.convert(end, msg), str.length());
99 }
100 if (start >= stop) {
101 return "";
102 }
103 return str.substring(start, stop);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100104 }
105
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000106 public static int getListIndex(Object key, int listSize, Location loc)
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100107 throws ConversionException, EvalException {
108 // Get the nth element in the list
109 int index = Type.INTEGER.convert(key, "index operand");
110 if (index < 0) {
111 index += listSize;
112 }
113 if (index < 0 || index >= listSize) {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000114 throw new EvalException(loc, "List index out of range (index is "
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100115 + index + ", but list has " + listSize + " elements)");
116 }
117 return index;
118 }
119
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000120 // supported string methods
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100121
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000122 @SkylarkSignature(name = "join", objectType = StringModule.class, returnType = String.class,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100123 doc = "Returns a string in which the string elements of the argument have been "
124 + "joined by this string as a separator. Example:<br>"
Laurent Le Brun9d27a012015-03-31 12:28:02 +0000125 + "<pre class=\"language-python\">\"|\".join([\"a\", \"b\", \"c\"]) == \"a|b|c\"</pre>",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000126 mandatoryPositionals = {
127 @Param(name = "self", type = String.class, doc = "This string, a separator."),
128 @Param(name = "elements", type = HackHackEitherList.class, doc = "The objects to join.")})
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000129 private static BuiltinFunction join = new BuiltinFunction("join") {
130 public String invoke(String self, Object elements) throws ConversionException {
131 List<?> seq = Type.OBJECT_LIST.convert(elements, "'join.elements' argument");
132 return Joiner.on(self).join(seq);
133 }
134 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100135
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000136 @SkylarkSignature(name = "lower", objectType = StringModule.class, returnType = String.class,
137 doc = "Returns the lower case version of this string.",
138 mandatoryPositionals = {
139 @Param(name = "self", type = String.class, doc = "This string, to convert to lower case.")})
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000140 private static BuiltinFunction lower = new BuiltinFunction("lower") {
141 public String invoke(String self) {
142 return self.toLowerCase();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100143 }
144 };
145
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000146 @SkylarkSignature(name = "upper", objectType = StringModule.class, returnType = String.class,
147 doc = "Returns the upper case version of this string.",
148 mandatoryPositionals = {
149 @Param(name = "self", type = String.class, doc = "This string, to convert to upper case.")})
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000150 private static BuiltinFunction upper = new BuiltinFunction("upper") {
151 public String invoke(String self) {
152 return self.toUpperCase();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100153 }
154 };
155
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000156 @SkylarkSignature(name = "replace", objectType = StringModule.class, returnType = String.class,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100157 doc = "Returns a copy of the string in which the occurrences "
158 + "of <code>old</code> have been replaced with <code>new</code>, optionally restricting "
159 + "the number of replacements to <code>maxsplit</code>.",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000160 mandatoryPositionals = {
161 @Param(name = "self", type = String.class, doc = "This string."),
162 @Param(name = "old", type = String.class, doc = "The string to be replaced."),
163 @Param(name = "new", type = String.class, doc = "The string to replace with.")},
164 optionalPositionals = {
Francois-Rene Rideau534dca12015-04-21 19:43:19 +0000165 @Param(name = "maxsplit", type = Integer.class, noneable = true, defaultValue = "None",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000166 doc = "The maximum number of replacements.")},
167 useLocation = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000168 private static BuiltinFunction replace = new BuiltinFunction("replace") {
169 public String invoke(String self, String oldString, String newString, Object maxSplitO,
170 Location loc) throws EvalException, ConversionException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100171 StringBuffer sb = new StringBuffer();
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000172 Integer maxSplit = Type.INTEGER.convertOptional(
173 maxSplitO, "'maxsplit' argument of 'replace'", /*label*/null, Integer.MAX_VALUE);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100174 try {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000175 Matcher m = Pattern.compile(oldString, Pattern.LITERAL).matcher(self);
176 for (int i = 0; i < maxSplit && m.find(); i++) {
177 m.appendReplacement(sb, Matcher.quoteReplacement(newString));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100178 }
179 m.appendTail(sb);
180 } catch (IllegalStateException e) {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000181 throw new EvalException(loc, e.getMessage() + " in call to replace");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100182 }
183 return sb.toString();
184 }
185 };
186
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000187 @SkylarkSignature(name = "split", objectType = StringModule.class,
188 returnType = HackHackEitherList.class,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100189 doc = "Returns a list of all the words in the string, using <code>sep</code> "
190 + "as the separator, optionally limiting the number of splits to <code>maxsplit</code>.",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000191 mandatoryPositionals = {
192 @Param(name = "self", type = String.class, doc = "This string.")},
193 optionalPositionals = {
Francois-Rene Rideau534dca12015-04-21 19:43:19 +0000194 @Param(name = "sep", type = String.class, defaultValue = "' '",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000195 doc = "The string to split on, default is space (\" \")."),
Francois-Rene Rideau534dca12015-04-21 19:43:19 +0000196 @Param(name = "maxsplit", type = Integer.class, noneable = true, defaultValue = "None",
197 doc = "The maximum number of splits.")},
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000198 useEnvironment = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000199 private static BuiltinFunction split = new BuiltinFunction("split") {
200 public Object invoke(String self, String sep, Object maxSplitO,
201 Environment env) throws ConversionException {
202 int maxSplit = Type.INTEGER.convertOptional(
203 maxSplitO, "'split' argument of 'split'", /*label*/null, -2);
204 // + 1 because the last result is the remainder, and default of -2 so that after +1 it's -1
205 String[] ss = Pattern.compile(sep, Pattern.LITERAL).split(self, maxSplit + 1);
206 List<String> result = Arrays.<String>asList(ss);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100207 return env.isSkylarkEnabled() ? SkylarkList.list(result, String.class) : result;
208 }
209 };
210
Francois-Rene Rideau76023b92015-04-17 15:31:59 +0000211 /**
212 * Common implementation for find, rfind, index, rindex.
213 * @param forward true if we want to return the last matching index.
214 */
215 private static int stringFind(boolean forward,
216 String self, String sub, int start, Object end, String msg)
217 throws ConversionException {
218 String substr = pythonSubstring(self, start, end, msg);
219 int subpos = forward ? substr.indexOf(sub) : substr.lastIndexOf(sub);
220 start = clampIndex(start, self.length());
221 return subpos < 0 ? subpos : subpos + start;
222 }
223
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000224 @SkylarkSignature(name = "rfind", objectType = StringModule.class, returnType = Integer.class,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100225 doc = "Returns the last index where <code>sub</code> is found, "
226 + "or -1 if no such index exists, optionally restricting to "
227 + "[<code>start</code>:<code>end</code>], "
228 + "<code>start</code> being inclusive and <code>end</code> being exclusive.",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000229 mandatoryPositionals = {
230 @Param(name = "self", type = String.class, doc = "This string."),
231 @Param(name = "sub", type = String.class, doc = "The substring to find.")},
232 optionalPositionals = {
233 @Param(name = "start", type = Integer.class, defaultValue = "0",
234 doc = "Restrict to search from this position."),
235 @Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None",
236 doc = "optional position before which to restrict to search.")})
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000237 private static BuiltinFunction rfind = new BuiltinFunction("rfind") {
238 public Integer invoke(String self, String sub, Integer start, Object end)
239 throws ConversionException {
240 return stringFind(false, self, sub, start, end, "'end' argument to rfind");
241 }
242 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100243
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000244 @SkylarkSignature(name = "find", objectType = StringModule.class, returnType = Integer.class,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100245 doc = "Returns the first index where <code>sub</code> is found, "
246 + "or -1 if no such index exists, optionally restricting to "
247 + "[<code>start</code>:<code>end]</code>, "
248 + "<code>start</code> being inclusive and <code>end</code> being exclusive.",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000249 mandatoryPositionals = {
250 @Param(name = "self", type = String.class, doc = "This string."),
251 @Param(name = "sub", type = String.class, doc = "The substring to find.")},
252 optionalPositionals = {
253 @Param(name = "start", type = Integer.class, defaultValue = "0",
254 doc = "Restrict to search from this position."),
255 @Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None",
256 doc = "optional position before which to restrict to search.")})
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000257 private static BuiltinFunction find = new BuiltinFunction("find") {
258 public Integer invoke(String self, String sub, Integer start, Object end)
259 throws ConversionException {
260 return stringFind(true, self, sub, start, end, "'end' argument to find");
261 }
262 };
Laurent Le Brun4e116c72015-03-23 13:48:50 +0000263
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000264 @SkylarkSignature(name = "rindex", objectType = StringModule.class, returnType = Integer.class,
Laurent Le Brun4e116c72015-03-23 13:48:50 +0000265 doc = "Returns the last index where <code>sub</code> is found, "
266 + "or throw an error if no such index exists, optionally restricting to "
267 + "[<code>start</code>:<code>end</code>], "
268 + "<code>start</code> being inclusive and <code>end</code> being exclusive.",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000269 mandatoryPositionals = {
270 @Param(name = "self", type = String.class, doc = "This string."),
271 @Param(name = "sub", type = String.class, doc = "The substring to find.")},
272 optionalPositionals = {
273 @Param(name = "start", type = Integer.class, defaultValue = "0",
274 doc = "Restrict to search from this position."),
275 @Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None",
276 doc = "optional position before which to restrict to search.")},
277 useLocation = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000278 private static BuiltinFunction rindex = new BuiltinFunction("rindex") {
279 public Integer invoke(String self, String sub, Integer start, Object end,
280 Location loc) throws EvalException, ConversionException {
281 int res = stringFind(false, self, sub, start, end, "'end' argument to rindex");
282 if (res < 0) {
283 throw new EvalException(loc, String.format("substring %s not found in %s",
284 EvalUtils.prettyPrintValue(sub), EvalUtils.prettyPrintValue(self)));
285 }
286 return res;
287 }
288 };
Laurent Le Brun4e116c72015-03-23 13:48:50 +0000289
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000290 @SkylarkSignature(name = "index", objectType = StringModule.class, returnType = Integer.class,
Laurent Le Brun4e116c72015-03-23 13:48:50 +0000291 doc = "Returns the first index where <code>sub</code> is found, "
292 + "or throw an error if no such index exists, optionally restricting to "
293 + "[<code>start</code>:<code>end]</code>, "
294 + "<code>start</code> being inclusive and <code>end</code> being exclusive.",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000295 mandatoryPositionals = {
296 @Param(name = "self", type = String.class, doc = "This string."),
297 @Param(name = "sub", type = String.class, doc = "The substring to find.")},
298 optionalPositionals = {
299 @Param(name = "start", type = Integer.class, defaultValue = "0",
300 doc = "Restrict to search from this position."),
301 @Param(name = "end", type = Integer.class, noneable = true,
302 doc = "optional position before which to restrict to search.")},
303 useLocation = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000304 private static BuiltinFunction index = new BuiltinFunction("index") {
305 public Integer invoke(String self, String sub, Integer start, Object end,
306 Location loc) throws EvalException, ConversionException {
307 int res = stringFind(true, self, sub, start, end, "'end' argument to index");
308 if (res < 0) {
309 throw new EvalException(loc, String.format("substring %s not found in %s",
310 EvalUtils.prettyPrintValue(sub), EvalUtils.prettyPrintValue(self)));
311 }
312 return res;
313 }
314 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100315
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000316 @SkylarkSignature(name = "count", objectType = StringModule.class, returnType = Integer.class,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100317 doc = "Returns the number of (non-overlapping) occurrences of substring <code>sub</code> in "
318 + "string, optionally restricting to [<code>start</code>:<code>end</code>], "
319 + "<code>start</code> being inclusive and <code>end</code> being exclusive.",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000320 mandatoryPositionals = {
321 @Param(name = "self", type = String.class, doc = "This string."),
322 @Param(name = "sub", type = String.class, doc = "The substring to count.")},
323 optionalPositionals = {
324 @Param(name = "start", type = Integer.class, defaultValue = "0",
325 doc = "Restrict to search from this position."),
326 @Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None",
327 doc = "optional position before which to restrict to search.")})
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000328 private static BuiltinFunction count = new BuiltinFunction("count") {
329 public Integer invoke(String self, String sub, Integer start, Object end)
330 throws ConversionException {
331 String str = pythonSubstring(self, start, end, "'end' operand of 'find'");
332 if (sub.isEmpty()) {
333 return str.length() + 1;
334 }
335 int count = 0;
336 int index = -1;
337 while ((index = str.indexOf(sub)) >= 0) {
338 count++;
339 str = str.substring(index + sub.length());
340 }
341 return count;
342 }
343 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100344
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000345 @SkylarkSignature(name = "endswith", objectType = StringModule.class, returnType = Boolean.class,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100346 doc = "Returns True if the string ends with <code>sub</code>, "
347 + "otherwise False, optionally restricting to [<code>start</code>:<code>end</code>], "
348 + "<code>start</code> being inclusive and <code>end</code> being exclusive.",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000349 mandatoryPositionals = {
350 @Param(name = "self", type = String.class, doc = "This string."),
351 @Param(name = "sub", type = String.class, doc = "The substring to check.")},
352 optionalPositionals = {
353 @Param(name = "start", type = Integer.class, defaultValue = "0",
354 doc = "Test beginning at this position."),
355 @Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None",
356 doc = "optional position at which to stop comparing.")})
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000357 private static BuiltinFunction endswith = new BuiltinFunction(
358 "endswith", SkylarkList.tuple(0, Environment.NONE)) {
359 public Boolean invoke(String self, String sub, Integer start, Object end)
360 throws ConversionException {
361 return pythonSubstring(self, start, end, "'end' operand of 'endswith'").endsWith(sub);
362 }
363 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100364
Laurent Le Brunaa83c3a2015-05-08 15:00:35 +0000365 // In Python, formatting is very complex.
366 // We handle here the simplest case which provides most of the value of the function.
367 // https://docs.python.org/3/library/string.html#formatstrings
368 @SkylarkSignature(name = "format", objectType = StringModule.class, returnType = String.class,
369 doc = "Replace the values surrounded by curly brackets in the string."
370 + "<pre class=\"language-python\">"
371 + "\"x{key}x\".format(key = 2) == \"x2x\"</pre>\n",
372 mandatoryPositionals = {
373 @Param(name = "self", type = String.class, doc = "This string."),
374 },
375 extraKeywords = {
376 @Param(name = "kwargs", doc = "the struct fields")},
377 useLocation = true)
378 private static BuiltinFunction format = new BuiltinFunction("format") {
379 public String invoke(String self, Map<String, Object> kwargs, Location loc)
380 throws ConversionException, EvalException {
381 StringBuffer result = new StringBuffer();
382 Pattern pattern = Pattern.compile("\\{[^}]*\\}");
383 Matcher matcher = pattern.matcher(self);
384 while (matcher.find()) {
385 String word = matcher.group();
386 word = word.substring(1, word.length() - 1); // remove the curly braces
387 if (!kwargs.containsKey(word)) {
388 throw new EvalException(loc, "No replacement found for '" + word + "'");
389 }
390 matcher.appendReplacement(result, EvalUtils.printValue(kwargs.get(word)));
391 }
392 matcher.appendTail(result);
393 return result.toString();
394 }
395 };
396
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000397 @SkylarkSignature(name = "startswith", objectType = StringModule.class,
398 returnType = Boolean.class,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100399 doc = "Returns True if the string starts with <code>sub</code>, "
400 + "otherwise False, optionally restricting to [<code>start</code>:<code>end</code>], "
401 + "<code>start</code> being inclusive and <code>end</code> being exclusive.",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000402 mandatoryPositionals = {
403 @Param(name = "self", type = String.class, doc = "This string."),
404 @Param(name = "sub", type = String.class, doc = "The substring to check.")},
405 optionalPositionals = {
406 @Param(name = "start", type = Integer.class, defaultValue = "0",
407 doc = "Test beginning at this position."),
408 @Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None",
409 doc = "Stop comparing at this position.")})
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000410 private static BuiltinFunction startswith = new BuiltinFunction("startswith") {
411 public Boolean invoke(String self, String sub, Integer start, Object end)
412 throws ConversionException {
413 return pythonSubstring(self, start, end, "'end' operand of 'startswith'").startsWith(sub);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100414 }
415 };
416
417 // TODO(bazel-team): Maybe support an argument to tell the type of the whitespace.
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000418 @SkylarkSignature(name = "strip", objectType = StringModule.class, returnType = String.class,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100419 doc = "Returns a copy of the string in which all whitespace characters "
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000420 + "have been stripped from the beginning and the end of the string.",
421 mandatoryPositionals = {
422 @Param(name = "self", type = String.class, doc = "This string.")})
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000423 private static BuiltinFunction strip = new BuiltinFunction("strip") {
424 public String invoke(String self) {
425 return self.trim();
426 }
427 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100428
Laurent Le Bruneeef30f2015-03-16 15:12:35 +0000429 // slice operator
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000430 @SkylarkSignature(name = "$slice", documented = false,
431 mandatoryPositionals = {
432 @Param(name = "self", type = Object.class, doc = "This string, list or tuple."),
433 @Param(name = "start", type = Integer.class, doc = "start position of the slice."),
434 @Param(name = "end", type = Integer.class, doc = "end position of the slice.")},
435 doc = "x[<code>start</code>:<code>end</code>] returns a slice or a list slice.",
436 useLocation = true, useEnvironment = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000437 private static BuiltinFunction slice = new BuiltinFunction("$slice") {
438 public Object invoke(Object self, Integer left, Integer right,
439 Location loc, Environment env) throws EvalException, ConversionException {
Laurent Le Bruneeef30f2015-03-16 15:12:35 +0000440 // Substring
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000441 if (self instanceof String) {
442 return pythonSubstring((String) self, left, right, "");
Laurent Le Bruneeef30f2015-03-16 15:12:35 +0000443 }
444
445 // List slice
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000446 List<Object> list = Type.OBJECT_LIST.convert(self, "list operand");
Francois-Rene Rideau76023b92015-04-17 15:31:59 +0000447 left = clampIndex(left, list.size());
448 right = clampIndex(right, list.size());
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000449 if (left > right) {
450 left = right;
Laurent Le Bruneeef30f2015-03-16 15:12:35 +0000451 }
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000452 return convert(list.subList(left, right), env, loc);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100453 }
454 };
455
456 // supported list methods
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000457 @SkylarkSignature(name = "sorted", returnType = HackHackEitherList.class,
458 doc = "Sort a collection.",
459 mandatoryPositionals = {
460 @Param(name = "self", type = HackHackEitherList.class, doc = "This list.")},
461 useLocation = true, useEnvironment = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000462 private static BuiltinFunction sorted = new BuiltinFunction("sorted") {
463 public Object invoke(Object self, Location loc, Environment env)
Laurent Le Brunef69ec52015-04-16 18:58:34 +0000464 throws EvalException, ConversionException {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000465 List<Object> list = Type.OBJECT_LIST.convert(self, "'sorted' operand");
Laurent Le Brunef69ec52015-04-16 18:58:34 +0000466 try {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000467 list = Ordering.from(EvalUtils.SKYLARK_COMPARATOR).sortedCopy(list);
Laurent Le Brunef69ec52015-04-16 18:58:34 +0000468 } catch (EvalUtils.ComparisonException e) {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000469 throw new EvalException(loc, e);
Laurent Le Brunef69ec52015-04-16 18:58:34 +0000470 }
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000471 return convert(list, env, loc);
Laurent Le Brunef69ec52015-04-16 18:58:34 +0000472 }
473 };
474
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000475 // This function has a SkylarkSignature but is only used by the Build language, not Skylark.
476 @SkylarkSignature(name = "append", returnType = Environment.NoneType.class, documented = false,
477 doc = "Adds an item to the end of the list.",
478 mandatoryPositionals = {
479 // we use List rather than SkylarkList because this is actually for use *outside* Skylark
480 @Param(name = "self", type = List.class, doc = "This list."),
481 @Param(name = "item", type = Object.class, doc = "Item to add at the end.")},
482 useLocation = true, useEnvironment = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000483 private static BuiltinFunction append = new BuiltinFunction("append") {
484 public Environment.NoneType invoke(List<Object> self, Object item,
485 Location loc, Environment env) throws EvalException, ConversionException {
486 self.add(item);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100487 return Environment.NONE;
488 }
489 };
490
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000491 // This function has a SkylarkSignature but is only used by the Build language, not Skylark.
492 @SkylarkSignature(name = "extend", returnType = Environment.NoneType.class, documented = false,
493 doc = "Adds all items to the end of the list.",
494 mandatoryPositionals = {
495 // we use List rather than SkylarkList because this is actually for use *outside* Skylark
496 @Param(name = "self", type = List.class, doc = "This list."),
497 @Param(name = "items", type = List.class, doc = "Items to add at the end.")},
498 useLocation = true, useEnvironment = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000499 private static BuiltinFunction extend = new BuiltinFunction("extend") {
500 public Environment.NoneType invoke(List<Object> self, List<Object> items,
501 Location loc, Environment env) throws EvalException, ConversionException {
502 self.addAll(items);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100503 return Environment.NONE;
504 }
505 };
506
507 // dictionary access operator
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000508 @SkylarkSignature(name = "$index", documented = false,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100509 doc = "Returns the nth element of a list or string, "
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000510 + "or looks up a value in a dictionary.",
511 mandatoryPositionals = {
512 @Param(name = "self", type = Object.class, doc = "This object."),
513 @Param(name = "key", type = Object.class, doc = "The index or key to access.")},
514 useLocation = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000515 private static BuiltinFunction indexOperator = new BuiltinFunction("$index") {
516 public Object invoke(Object self, Object key,
517 Location loc) throws EvalException, ConversionException {
518 if (self instanceof SkylarkList) {
519 SkylarkList list = (SkylarkList) self;
520 if (list.isEmpty()) {
521 throw new EvalException(loc, "List is empty");
522 }
523 int index = getListIndex(key, list.size(), loc);
524 return list.get(index);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100525
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000526 } else if (self instanceof Map<?, ?>) {
527 Map<?, ?> dictionary = (Map<?, ?>) self;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100528 if (!dictionary.containsKey(key)) {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000529 throw new EvalException(loc, String.format("Key %s not found in dictionary",
530 EvalUtils.prettyPrintValue(key)));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100531 }
532 return dictionary.get(key);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100533
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000534 } else if (self instanceof List<?>) {
535 List<Object> list = Type.OBJECT_LIST.convert(self, "index operand");
536 if (list.isEmpty()) {
537 throw new EvalException(loc, "List is empty");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100538 }
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000539 int index = getListIndex(key, list.size(), loc);
540 return list.get(index);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100541
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000542 } else if (self instanceof String) {
543 String str = (String) self;
544 int index = getListIndex(key, str.length(), loc);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100545 return str.substring(index, index + 1);
546
547 } else {
548 // TODO(bazel-team): This is dead code, get rid of it.
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000549 throw new EvalException(loc, String.format(
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100550 "Unsupported datatype (%s) for indexing, only works for dict and list",
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000551 EvalUtils.getDataTypeName(self)));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100552 }
553 }
554 };
555
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000556 @SkylarkSignature(name = "values", objectType = DictModule.class,
557 returnType = HackHackEitherList.class,
Laurent Le Brun9d27a012015-03-31 12:28:02 +0000558 doc = "Return the list of values. Dictionaries are always sorted by their keys:"
559 + "<pre class=\"language-python\">"
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000560 + "{2: \"a\", 4: \"b\", 1: \"c\"}.values() == [\"c\", \"a\", \"b\"]</pre>\n",
561 mandatoryPositionals = {@Param(name = "self", type = Map.class, doc = "This dict.")},
562 useLocation = true, useEnvironment = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000563 private static BuiltinFunction values = new BuiltinFunction("values") {
564 public Object invoke(Map<?, ?> self,
565 Location loc, Environment env) throws EvalException, ConversionException {
Laurent Le Brun196c1a72015-03-18 13:03:04 +0000566 // Use a TreeMap to ensure consistent ordering.
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000567 Map<?, ?> dict = new TreeMap<>(self);
568 return convert(dict.values(), env, loc);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100569 }
570 };
571
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000572 @SkylarkSignature(name = "items", objectType = DictModule.class,
573 returnType = HackHackEitherList.class,
Laurent Le Brun9d27a012015-03-31 12:28:02 +0000574 doc = "Return the list of key-value tuples. Dictionaries are always sorted by their keys:"
575 + "<pre class=\"language-python\">"
576 + "{2: \"a\", 4: \"b\", 1: \"c\"}.items() == [(1, \"c\"), (2, \"a\"), (4, \"b\")]"
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000577 + "</pre>\n",
578 mandatoryPositionals = {
579 @Param(name = "self", type = Map.class, doc = "This dict.")},
580 useLocation = true, useEnvironment = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000581 private static BuiltinFunction items = new BuiltinFunction("items") {
582 public Object invoke(Map<?, ?> self,
583 Location loc, Environment env) throws EvalException, ConversionException {
Laurent Le Brun196c1a72015-03-18 13:03:04 +0000584 // Use a TreeMap to ensure consistent ordering.
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000585 Map<?, ?> dict = new TreeMap<>(self);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100586 List<Object> list = Lists.newArrayListWithCapacity(dict.size());
587 for (Map.Entry<?, ?> entries : dict.entrySet()) {
588 List<?> item = ImmutableList.of(entries.getKey(), entries.getValue());
589 list.add(env.isSkylarkEnabled() ? SkylarkList.tuple(item) : item);
590 }
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000591 return convert(list, env, loc);
592 }
593 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100594
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000595 @SkylarkSignature(name = "keys", objectType = DictModule.class,
596 returnType = HackHackEitherList.class,
Laurent Le Brun9d27a012015-03-31 12:28:02 +0000597 doc = "Return the list of keys. Dictionaries are always sorted by their keys:"
598 + "<pre class=\"language-python\">{2: \"a\", 4: \"b\", 1: \"c\"}.keys() == [1, 2, 4]"
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000599 + "</pre>\n",
600 mandatoryPositionals = {
601 @Param(name = "self", type = Map.class, doc = "This dict.")},
602 useLocation = true, useEnvironment = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000603 // Skylark will only call this on a dict; and
604 // allowed keys are all Comparable... if not mutually, it's OK to get a runtime exception.
605 private static BuiltinFunction keys = new BuiltinFunction("keys") {
606 public Object invoke(Map<Comparable<?>, ?> dict,
607 Location loc, Environment env) throws EvalException {
608 return convert(Ordering.natural().sortedCopy(dict.keySet()), env, loc);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100609 }
610 };
611
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000612 @SkylarkSignature(name = "get", objectType = DictModule.class,
Laurent Le Brunf9148972015-03-31 13:54:36 +0000613 doc = "Return the value for <code>key</code> if <code>key</code> is in the dictionary, "
614 + "else <code>default</code>. If <code>default</code> is not given, it defaults to "
615 + "<code>None</code>, so that this method never throws an error.",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000616 mandatoryPositionals = {
617 @Param(name = "self", doc = "This dict."),
Laurent Le Brunf9148972015-03-31 13:54:36 +0000618 @Param(name = "key", doc = "The key to look for.")},
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000619 optionalPositionals = {
620 @Param(name = "default", defaultValue = "None",
Laurent Le Brunf9148972015-03-31 13:54:36 +0000621 doc = "The default value to use (instead of None) if the key is not found.")})
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000622 private static BuiltinFunction get = new BuiltinFunction("get") {
623 public Object invoke(Map<?, ?> self, Object key, Object defaultValue) {
624 if (self.containsKey(key)) {
625 return self.get(key);
Laurent Le Brunf9148972015-03-31 13:54:36 +0000626 }
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000627 return defaultValue;
Laurent Le Brunf9148972015-03-31 13:54:36 +0000628 }
629 };
630
Laurent Le Bruneeef30f2015-03-16 15:12:35 +0000631 // TODO(bazel-team): Use the same type for both Skylark and BUILD files.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100632 @SuppressWarnings("unchecked")
633 private static Iterable<Object> convert(Collection<?> list, Environment env, Location loc)
634 throws EvalException {
635 if (env.isSkylarkEnabled()) {
636 return SkylarkList.list(list, loc);
637 } else {
638 return Lists.newArrayList(list);
639 }
640 }
641
642 // unary minus
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000643 @SkylarkSignature(name = "-", returnType = Integer.class,
644 documented = false, doc = "Unary minus operator.",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000645 mandatoryPositionals = {
646 @Param(name = "num", type = Integer.class, doc = "The number to negate.")})
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000647 private static BuiltinFunction minus = new BuiltinFunction("-") {
648 public Integer invoke(Integer num) throws ConversionException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100649 return -num;
650 }
651 };
652
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000653 @SkylarkSignature(name = "list", returnType = SkylarkList.class,
Laurent Le Brun9d27a012015-03-31 12:28:02 +0000654 doc = "Converts a collection (e.g. set or dictionary) to a list."
655 + "<pre class=\"language-python\">list([1, 2]) == [1, 2]\n"
656 + "list(set([2, 3, 2])) == [2, 3]\n"
657 + "list({5: \"a\", 2: \"b\", 4: \"c\"}) == [2, 4, 5]</pre>",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000658 mandatoryPositionals = {@Param(name = "x", doc = "The object to convert.")},
659 useLocation = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000660 private static BuiltinFunction list = new BuiltinFunction("list") {
661 public SkylarkList invoke(Object x, Location loc) throws EvalException {
662 return SkylarkList.list(EvalUtils.toCollection(x, loc), loc);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100663 }
664 };
665
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000666 @SkylarkSignature(name = "len", returnType = Integer.class, doc =
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100667 "Returns the length of a string, list, tuple, set, or dictionary.",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000668 mandatoryPositionals = {@Param(name = "x", doc = "The object to check length of.")},
669 useLocation = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000670 private static BuiltinFunction len = new BuiltinFunction("len") {
671 public Integer invoke(Object x, Location loc) throws EvalException {
672 int l = EvalUtils.size(x);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100673 if (l == -1) {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000674 throw new EvalException(loc, EvalUtils.getDataTypeName(x) + " is not iterable");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100675 }
676 return l;
677 }
678 };
679
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000680 @SkylarkSignature(name = "str", returnType = String.class, doc =
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100681 "Converts any object to string. This is useful for debugging.",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000682 mandatoryPositionals = {@Param(name = "x", doc = "The object to convert.")})
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000683 private static BuiltinFunction str = new BuiltinFunction("str") {
684 public String invoke(Object x) throws EvalException {
685 return EvalUtils.printValue(x);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100686 }
687 };
688
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000689 @SkylarkSignature(name = "bool", returnType = Boolean.class,
690 doc = "Converts an object to boolean. "
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100691 + "It returns False if the object is None, False, an empty string, the number 0, or an "
Googlerc4cf9132015-03-20 14:54:23 +0000692 + "empty collection. Otherwise, it returns True. Similarly to Python <code>bool</code> "
693 + "is also a type.",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000694 mandatoryPositionals = {@Param(name = "x", doc = "The variable to convert.")})
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000695 private static BuiltinFunction bool = new BuiltinFunction("bool") {
696 public Boolean invoke(Object x) throws EvalException {
697 return EvalUtils.toBoolean(x);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100698 }
699 };
700
Laurent Le Brunf4648de2015-05-07 14:00:32 +0000701 @SkylarkSignature(name = "int", returnType = Integer.class, doc = "Converts a value to int. "
702 + "If the argument is a string, it is converted using base 10 and raises an error if the "
703 + "conversion fails. If the argument is a bool, it returns 0 (False) or 1 (True). "
704 + "If the argument is an int, it is simply returned."
Laurent Le Brun0c44aa42015-04-02 11:32:47 +0000705 + "<pre class=\"language-python\">int(\"123\") == 123</pre>",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000706 mandatoryPositionals = {
Laurent Le Brunf4648de2015-05-07 14:00:32 +0000707 @Param(name = "x", type = Object.class, doc = "The string to convert.")},
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000708 useLocation = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000709 private static BuiltinFunction int_ = new BuiltinFunction("int") {
Laurent Le Brunf4648de2015-05-07 14:00:32 +0000710 public Integer invoke(Object x, Location loc) throws EvalException {
711 if (x instanceof Boolean) {
712 return ((Boolean) x).booleanValue() ? 1 : 0;
713 } else if (x instanceof Integer) {
714 return (Integer) x;
715 } else if (x instanceof String) {
716 try {
717 return Integer.parseInt((String) x);
718 } catch (NumberFormatException e) {
719 throw new EvalException(loc,
720 "invalid literal for int(): " + EvalUtils.prettyPrintValue(x));
721 }
722 } else {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000723 throw new EvalException(loc,
Laurent Le Brunf4648de2015-05-07 14:00:32 +0000724 String.format("argument must be string, int, or bool, not '%s'",
725 EvalUtils.prettyPrintValue(x)));
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000726 }
727 }
728 };
Laurent Le Brun0c44aa42015-04-02 11:32:47 +0000729
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000730 @SkylarkSignature(name = "struct", returnType = SkylarkClassObject.class, doc =
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100731 "Creates an immutable struct using the keyword arguments as fields. It is used to group "
732 + "multiple values together.Example:<br>"
Laurent Le Brun9d27a012015-03-31 12:28:02 +0000733 + "<pre class=\"language-python\">s = struct(x = 2, y = 3)\n"
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000734 + "return s.x + s.y # returns 5</pre>",
735 extraKeywords = {
Laurent Le Brunaa83c3a2015-05-08 15:00:35 +0000736 @Param(name = "kwargs", doc = "the struct fields")},
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000737 useLocation = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000738 private static BuiltinFunction struct = new BuiltinFunction("struct") {
739 @SuppressWarnings("unchecked")
740 public SkylarkClassObject invoke(Map<String, Object> kwargs, Location loc)
741 throws EvalException, InterruptedException {
742 return new SkylarkClassObject(kwargs, loc);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100743 }
744 };
745
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000746 @SkylarkSignature(name = "set", returnType = SkylarkNestedSet.class,
Laurent Le Brun7ace1f22015-04-09 13:54:21 +0000747 doc = "Creates a <a href=\"#modules.set\">set</a> from the <code>items</code>."
748 + " The set supports nesting other sets of the same element"
749 + " type in it. A desired iteration order can also be specified.<br>"
750 + " Examples:<br><pre class=\"language-python\">set([\"a\", \"b\"])\n"
Laszlo Csomoraded1c22015-02-16 16:57:12 +0000751 + "set([1, 2, 3], order=\"compile\")</pre>",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000752 optionalPositionals = {
753 @Param(name = "items", type = Object.class, defaultValue = "[]",
754 doc = "The items to initialize the set with. May contain both standalone items "
755 + "and other sets."),
756 @Param(name = "order", type = String.class, defaultValue = "\"stable\"",
757 doc = "The ordering strategy for the set if it's nested, "
758 + "possible values are: <code>stable</code> (default), <code>compile</code>, "
759 + "<code>link</code> or <code>naive_link</code>.")},
760 useLocation = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000761 private static final BuiltinFunction set = new BuiltinFunction("set") {
762 public SkylarkNestedSet invoke(Object items, String order,
763 Location loc) throws EvalException, ConversionException {
764 return new SkylarkNestedSet(SkylarkNestedSet.parseOrder(order, loc), items, loc);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100765 }
766 };
767
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000768 @SkylarkSignature(name = "enumerate", returnType = SkylarkList.class,
Laszlo Csomoraded1c22015-02-16 16:57:12 +0000769 doc = "Return a list of pairs (two-element lists), with the index (int) and the item from"
Laurent Le Brun9d27a012015-03-31 12:28:02 +0000770 + " the input list.\n<pre class=\"language-python\">"
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100771 + "enumerate([24, 21, 84]) == [[0, 24], [1, 21], [2, 84]]</pre>\n",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000772 mandatoryPositionals = {@Param(name = "list", type = SkylarkList.class, doc = "input list")},
773 useLocation = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000774 private static BuiltinFunction enumerate = new BuiltinFunction("enumerate") {
775 public SkylarkList invoke(SkylarkList input, Location loc)
776 throws EvalException, ConversionException, InterruptedException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100777 int count = 0;
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000778 List<SkylarkList> result = Lists.newArrayList();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100779 for (Object obj : input) {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000780 result.add(SkylarkList.tuple(count, obj));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100781 count++;
782 }
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000783 return SkylarkList.list(result, loc);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100784 }
785 };
786
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000787 @SkylarkSignature(name = "range", returnType = SkylarkList.class,
Laszlo Csomor52fb6a22015-02-17 09:46:49 +0000788 doc = "Creates a list where items go from <code>start</code> to <code>stop</code>, using a "
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100789 + "<code>step</code> increment. If a single argument is provided, items will "
790 + "range from 0 to that element."
Laurent Le Brun9d27a012015-03-31 12:28:02 +0000791 + "<pre class=\"language-python\">range(4) == [0, 1, 2, 3]\n"
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100792 + "range(3, 9, 2) == [3, 5, 7]\n"
793 + "range(3, 0, -1) == [3, 2, 1]</pre>",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000794 mandatoryPositionals = {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000795 @Param(name = "start_or_stop", type = Integer.class,
796 doc = "Value of the start element if stop is provided, "
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000797 + "otherwise value of stop and the actual start is 0"),
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100798 },
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000799 optionalPositionals = {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000800 @Param(name = "stop_or_none", type = Integer.class, noneable = true, defaultValue = "None",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000801 doc = "optional index of the first item <i>not</i> to be included in the "
802 + "resulting list; generation of the list stops before <code>stop</code> is reached."),
Francois-Rene Rideau534dca12015-04-21 19:43:19 +0000803 @Param(name = "step", type = Integer.class, defaultValue = "1",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000804 doc = "The increment (default is 1). It may be negative.")},
805 useLocation = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000806 private static final BuiltinFunction range = new BuiltinFunction("range") {
807 public SkylarkList invoke(Integer startOrStop, Object stopOrNone, Integer step, Location loc)
808 throws EvalException, ConversionException, InterruptedException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100809 int start;
810 int stop;
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000811 if (stopOrNone == Environment.NONE) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100812 start = 0;
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000813 stop = startOrStop;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100814 } else {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000815 start = startOrStop;
816 stop = Type.INTEGER.convert(stopOrNone, "'stop' operand of 'range'");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100817 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100818 if (step == 0) {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000819 throw new EvalException(loc, "step cannot be 0");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100820 }
821 List<Integer> result = Lists.newArrayList();
822 if (step > 0) {
823 while (start < stop) {
824 result.add(start);
825 start += step;
826 }
827 } else {
828 while (start > stop) {
829 result.add(start);
830 start += step;
831 }
832 }
833 return SkylarkList.list(result, Integer.class);
834 }
835 };
836
837 /**
838 * Returns a function-value implementing "select" (i.e. configurable attributes)
839 * in the specified package context.
840 */
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000841 @SkylarkSignature(name = "select",
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100842 doc = "Creates a SelectorValue from the dict parameter.",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000843 mandatoryPositionals = {
844 @Param(name = "x", type = Map.class, doc = "The parameter to convert.")})
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000845 private static final BuiltinFunction select = new BuiltinFunction("select") {
846 public Object invoke(Map<?, ?> dict) throws EvalException, InterruptedException {
847 return SelectorList.of(new SelectorValue(dict));
848 }
849 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100850
851 /**
852 * Returns true if the object has a field of the given name, otherwise false.
853 */
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000854 @SkylarkSignature(name = "hasattr", returnType = Boolean.class,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100855 doc = "Returns True if the object <code>x</code> has a field of the given <code>name</code>, "
856 + "otherwise False. Example:<br>"
Laurent Le Brun9d27a012015-03-31 12:28:02 +0000857 + "<pre class=\"language-python\">hasattr(ctx.attr, \"myattr\")</pre>",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000858 mandatoryPositionals = {
859 @Param(name = "object", doc = "The object to check."),
860 @Param(name = "name", type = String.class, doc = "The name of the field.")},
861 useLocation = true, useEnvironment = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000862 private static final BuiltinFunction hasattr = new BuiltinFunction("hasattr") {
863 public Boolean invoke(Object obj, String name,
864 Location loc, Environment env) throws EvalException, ConversionException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100865 if (obj instanceof ClassObject && ((ClassObject) obj).getValue(name) != null) {
866 return true;
867 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100868 if (env.getFunctionNames(obj.getClass()).contains(name)) {
869 return true;
870 }
871
872 try {
873 return FuncallExpression.getMethodNames(obj.getClass()).contains(name);
874 } catch (ExecutionException e) {
875 // This shouldn't happen
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000876 throw new EvalException(loc, e.getMessage());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100877 }
878 }
879 };
880
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000881 @SkylarkSignature(name = "getattr",
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100882 doc = "Returns the struct's field of the given name if exists, otherwise <code>default</code>"
883 + " if specified, otherwise rasies an error. For example, <code>getattr(x, \"foobar\")"
884 + "</code> is equivalent to <code>x.foobar</code>."
885 + "Example:<br>"
Laurent Le Brun9d27a012015-03-31 12:28:02 +0000886 + "<pre class=\"language-python\">getattr(ctx.attr, \"myattr\")\n"
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100887 + "getattr(ctx.attr, \"myattr\", \"mydefault\")</pre>",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000888 mandatoryPositionals = {
889 @Param(name = "object", doc = "The struct which's field is accessed."),
890 @Param(name = "name", doc = "The name of the struct field.")},
891 optionalPositionals = {
Francois-Rene Rideau534dca12015-04-21 19:43:19 +0000892 @Param(name = "default", defaultValue = "None",
893 doc = "The default value to return in case the struct "
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000894 + "doesn't have a field of the given name.")},
895 useLocation = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000896 private static final BuiltinFunction getattr = new BuiltinFunction("getattr") {
897 public Object invoke(Object obj, String name, Object defaultValue,
898 Location loc) throws EvalException, ConversionException {
899 Object result = DotExpression.eval(obj, name, loc);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100900 if (result == null) {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000901 if (defaultValue != Environment.NONE) {
902 return defaultValue;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100903 } else {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000904 throw new EvalException(loc, String.format("Object of type '%s' has no field %s",
Francois-Rene Rideau534dca12015-04-21 19:43:19 +0000905 EvalUtils.getDataTypeName(obj), EvalUtils.prettyPrintValue(name)));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100906 }
907 }
908 return result;
909 }
910 };
911
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000912 @SkylarkSignature(name = "dir", returnType = SkylarkList.class,
Laszlo Csomoraded1c22015-02-16 16:57:12 +0000913 doc = "Returns a list strings: the names of the fields and "
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100914 + "methods of the parameter object.",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000915 mandatoryPositionals = {@Param(name = "object", doc = "The object to check.")},
916 useLocation = true, useEnvironment = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000917 private static final BuiltinFunction dir = new BuiltinFunction("dir") {
918 public SkylarkList invoke(Object object,
919 Location loc, Environment env) throws EvalException, ConversionException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100920 // Order the fields alphabetically.
921 Set<String> fields = new TreeSet<>();
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000922 if (object instanceof ClassObject) {
923 fields.addAll(((ClassObject) object).getKeys());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100924 }
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000925 fields.addAll(env.getFunctionNames(object.getClass()));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100926 try {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000927 fields.addAll(FuncallExpression.getMethodNames(object.getClass()));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100928 } catch (ExecutionException e) {
929 // This shouldn't happen
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000930 throw new EvalException(loc, e.getMessage());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100931 }
932 return SkylarkList.list(fields, String.class);
933 }
934 };
935
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000936 @SkylarkSignature(name = "type", returnType = String.class,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100937 doc = "Returns the type name of its argument.",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000938 mandatoryPositionals = {@Param(name = "object", doc = "The object to check type of.")})
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000939 private static final BuiltinFunction type = new BuiltinFunction("type") {
940 public String invoke(Object object) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100941 // There is no 'type' type in Skylark, so we return a string with the type name.
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000942 return EvalUtils.getDataTypeName(object, false);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100943 }
944 };
945
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000946 @SkylarkSignature(name = "fail",
Laurent Le Brun59fc7f32015-04-20 14:02:40 +0000947 doc = "Raises an error that cannot be intercepted.",
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100948 returnType = Environment.NoneType.class,
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000949 mandatoryPositionals = {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100950 @Param(name = "msg", type = String.class, doc = "Error message to display for the user")},
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000951 optionalPositionals = {
952 @Param(name = "attr", type = String.class, noneable = true,
953 defaultValue = "None",
954 doc = "The name of the attribute that caused the error")},
955 useLocation = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000956 private static final BuiltinFunction fail = new BuiltinFunction("fail") {
957 public Environment.NoneType invoke(String msg, Object attr,
958 Location loc) throws EvalException, ConversionException {
959 if (attr != Environment.NONE) {
960 msg = String.format("attribute %s: %s", attr, msg);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100961 }
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000962 throw new EvalException(loc, msg);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100963 }
964 };
965
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000966 @SkylarkSignature(name = "print", returnType = Environment.NoneType.class,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100967 doc = "Prints <code>msg</code> to the console.",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000968 optionalNamedOnly = {
Francois-Rene Rideau534dca12015-04-21 19:43:19 +0000969 @Param(name = "sep", type = String.class, defaultValue = "' '",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000970 doc = "The separator string between the objects, default is space (\" \").")},
971 // NB: as compared to Python3, we're missing optional named-only arguments 'end' and 'file'
972 extraPositionals = {@Param(name = "args", doc = "The objects to print.")},
973 useLocation = true, useEnvironment = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000974 private static final BuiltinFunction print = new BuiltinFunction("print") {
975 public Environment.NoneType invoke(String sep, SkylarkList starargs,
976 Location loc, SkylarkEnvironment env) throws EvalException {
977 String msg = Joiner.on(sep).join(Iterables.transform(starargs,
978 new com.google.common.base.Function<Object, String>() {
979 @Override
980 public String apply(Object input) {
981 return EvalUtils.printValue(input);
982 }}));
983 env.handleEvent(Event.warn(loc, msg));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100984 return Environment.NONE;
985 }
986 };
987
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000988 @SkylarkSignature(name = "zip",
Googlerc60ec8c2015-03-23 14:20:18 +0000989 doc = "Returns a <code>list</code> of <code>tuple</code>s, where the i-th tuple contains "
990 + "the i-th element from each of the argument sequences or iterables. The list has the "
991 + "size of the shortest input. With a single iterable argument, it returns a list of "
992 + "1-tuples. With no arguments, it returns an empty list. Examples:"
Laurent Le Brun9d27a012015-03-31 12:28:02 +0000993 + "<pre class=\"language-python\">"
Googlerc60ec8c2015-03-23 14:20:18 +0000994 + "zip() # == []\n"
995 + "zip([1, 2]) # == [(1,), (2,)]\n"
996 + "zip([1, 2], [3, 4]) # == [(1, 3), (2, 4)]\n"
997 + "zip([1, 2], [3, 4, 5]) # == [(1, 3), (2, 4)]</pre>",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000998 extraPositionals = {@Param(name = "args", doc = "lists to zip")},
999 returnType = SkylarkList.class, useLocation = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001000 private static final BuiltinFunction zip = new BuiltinFunction("zip") {
1001 public SkylarkList invoke(SkylarkList args, Location loc)
1002 throws EvalException, InterruptedException {
Googlerc60ec8c2015-03-23 14:20:18 +00001003 Iterator<?>[] iterators = new Iterator<?>[args.size()];
1004 for (int i = 0; i < args.size(); i++) {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001005 iterators[i] = EvalUtils.toIterable(args.get(i), loc).iterator();
Googlerc60ec8c2015-03-23 14:20:18 +00001006 }
1007 List<SkylarkList> result = new ArrayList<SkylarkList>();
1008 boolean allHasNext;
1009 do {
1010 allHasNext = !args.isEmpty();
1011 List<Object> elem = Lists.newArrayListWithExpectedSize(args.size());
1012 for (Iterator<?> iterator : iterators) {
1013 if (iterator.hasNext()) {
1014 elem.add(iterator.next());
1015 } else {
1016 allHasNext = false;
1017 }
1018 }
1019 if (allHasNext) {
1020 result.add(SkylarkList.tuple(elem));
1021 }
1022 } while (allHasNext);
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001023 return SkylarkList.list(result, loc);
Googlerc60ec8c2015-03-23 14:20:18 +00001024 }
1025 };
1026
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001027 /**
1028 * Skylark String module.
1029 */
1030 @SkylarkModule(name = "string", doc =
1031 "A language built-in type to support strings. "
Laurent Le Brun4848a652015-03-18 14:50:12 +00001032 + "Examples of string literals:<br>"
Laurent Le Brun9d27a012015-03-31 12:28:02 +00001033 + "<pre class=\"language-python\">a = 'abc\\ndef'\n"
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001034 + "b = \"ab'cd\"\n"
Laurent Le Brun4848a652015-03-18 14:50:12 +00001035 + "c = \"\"\"multiline string\"\"\"\n"
1036 + "\n"
1037 + "# Strings support slicing (negative index starts from the end):\n"
1038 + "x = \"hello\"[2:4] # \"ll\"\n"
1039 + "y = \"hello\"[1:-1] # \"ell\"\n"
1040 + "z = \"hello\"[:4] # \"hell\"</pre>"
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001041 + "Strings are iterable and support the <code>in</code> operator. Examples:<br>"
Laurent Le Brun9d27a012015-03-31 12:28:02 +00001042 + "<pre class=\"language-python\">\"a\" in \"abc\" # evaluates as True\n"
Laszlo Csomoraded1c22015-02-16 16:57:12 +00001043 + "x = []\n"
Laurent Le Brun9d27a012015-03-31 12:28:02 +00001044 + "for s in \"abc\":\n"
Laszlo Csomoraded1c22015-02-16 16:57:12 +00001045 + " x += [s] # x == [\"a\", \"b\", \"c\"]</pre>")
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001046 public static final class StringModule {}
1047
1048 /**
1049 * Skylark Dict module.
1050 */
1051 @SkylarkModule(name = "dict", doc =
1052 "A language built-in type to support dicts. "
1053 + "Example of dict literal:<br>"
Laurent Le Brun9d27a012015-03-31 12:28:02 +00001054 + "<pre class=\"language-python\">d = {\"a\": 2, \"b\": 5}</pre>"
1055 + "Use brackets to access elements:<br>"
1056 + "<pre class=\"language-python\">e = d[\"a\"] # e == 2</pre>"
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001057 + "Dicts support the <code>+</code> operator to concatenate two dicts. In case of multiple "
1058 + "keys the second one overrides the first one. Examples:<br>"
Laurent Le Brun9d27a012015-03-31 12:28:02 +00001059 + "<pre class=\"language-python\">"
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001060 + "d = {\"a\" : 1} + {\"b\" : 2} # d == {\"a\" : 1, \"b\" : 2}\n"
1061 + "d += {\"c\" : 3} # d == {\"a\" : 1, \"b\" : 2, \"c\" : 3}\n"
1062 + "d = d + {\"c\" : 5} # d == {\"a\" : 1, \"b\" : 2, \"c\" : 5}</pre>"
1063 + "Since the language doesn't have mutable objects <code>d[\"a\"] = 5</code> automatically "
1064 + "translates to <code>d = d + {\"a\" : 5}</code>.<br>"
Laurent Le Brun9d27a012015-03-31 12:28:02 +00001065 + "Iterating on a dict is equivalent to iterating on its keys (in sorted order).<br>"
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001066 + "Dicts support the <code>in</code> operator, testing membership in the keyset of the dict. "
1067 + "Example:<br>"
Laurent Le Brun9d27a012015-03-31 12:28:02 +00001068 + "<pre class=\"language-python\">\"a\" in {\"a\" : 2, \"b\" : 5} # evaluates as True"
1069 + "</pre>")
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001070 public static final class DictModule {}
1071
Francois-Rene Rideau95b0d0c2015-04-22 16:52:13 +00001072 public static final List<BaseFunction> stringFunctions = ImmutableList.<BaseFunction>of(
Laurent Le Brunaa83c3a2015-05-08 15:00:35 +00001073 count, endswith, find, index, format, join, lower, replace, rfind,
Laurent Le Brun89bdeaa2015-04-14 14:41:59 +00001074 rindex, slice, split, startswith, strip, upper);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001075
Francois-Rene Rideau95b0d0c2015-04-22 16:52:13 +00001076 public static final List<BaseFunction> listPureFunctions = ImmutableList.<BaseFunction>of(
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001077 slice);
Laurent Le Bruneeef30f2015-03-16 15:12:35 +00001078
Francois-Rene Rideau95b0d0c2015-04-22 16:52:13 +00001079 public static final List<BaseFunction> listFunctions = ImmutableList.<BaseFunction>of(
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001080 append, extend);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001081
Francois-Rene Rideau95b0d0c2015-04-22 16:52:13 +00001082 public static final List<BaseFunction> dictFunctions = ImmutableList.<BaseFunction>of(
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001083 items, get, keys, values);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001084
Francois-Rene Rideau95b0d0c2015-04-22 16:52:13 +00001085 private static final List<BaseFunction> pureGlobalFunctions = ImmutableList.<BaseFunction>of(
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001086 bool, int_, len, minus, select, sorted, str);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001087
Francois-Rene Rideau95b0d0c2015-04-22 16:52:13 +00001088 private static final List<BaseFunction> skylarkGlobalFunctions =
1089 ImmutableList.<BaseFunction>builder()
Laurent Le Brun352b9da2015-04-16 14:59:59 +00001090 .addAll(pureGlobalFunctions)
Francois-Rene Rideau534dca12015-04-21 19:43:19 +00001091 .add(list, struct, hasattr, getattr, set, dir, enumerate, range, type, fail, print, zip)
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001092 .build();
1093
1094 /**
1095 * Set up a given environment for supported class methods.
1096 */
1097 public static void setupMethodEnvironment(Environment env) {
Laurent Le Brun4e116c72015-03-23 13:48:50 +00001098 env.registerFunction(Map.class, indexOperator.getName(), indexOperator);
Laurent Le Brun89bdeaa2015-04-14 14:41:59 +00001099 setupMethodEnvironment(env, Map.class, dictFunctions);
Laurent Le Brun4e116c72015-03-23 13:48:50 +00001100 env.registerFunction(String.class, indexOperator.getName(), indexOperator);
Laurent Le Brun89bdeaa2015-04-14 14:41:59 +00001101 setupMethodEnvironment(env, String.class, stringFunctions);
1102 setupMethodEnvironment(env, List.class, listPureFunctions);
1103 setupMethodEnvironment(env, SkylarkList.class, listPureFunctions);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001104 if (env.isSkylarkEnabled()) {
Laurent Le Brun4e116c72015-03-23 13:48:50 +00001105 env.registerFunction(SkylarkList.class, indexOperator.getName(), indexOperator);
Laurent Le Brun352b9da2015-04-16 14:59:59 +00001106 setupMethodEnvironment(env, skylarkGlobalFunctions);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001107 } else {
Laurent Le Brun4e116c72015-03-23 13:48:50 +00001108 env.registerFunction(List.class, indexOperator.getName(), indexOperator);
1109 env.registerFunction(ImmutableList.class, indexOperator.getName(), indexOperator);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001110 // TODO(bazel-team): listFunctions are not allowed in Skylark extensions (use += instead).
1111 // It is allowed in BUILD files only for backward-compatibility.
1112 setupMethodEnvironment(env, List.class, listFunctions);
Laurent Le Brun352b9da2015-04-16 14:59:59 +00001113 setupMethodEnvironment(env, pureGlobalFunctions);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001114 }
1115 }
1116
1117 private static void setupMethodEnvironment(
Francois-Rene Rideau95b0d0c2015-04-22 16:52:13 +00001118 Environment env, Class<?> nameSpace, Iterable<BaseFunction> functions) {
1119 for (BaseFunction function : functions) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001120 env.registerFunction(nameSpace, function.getName(), function);
1121 }
1122 }
1123
Francois-Rene Rideau95b0d0c2015-04-22 16:52:13 +00001124 private static void setupMethodEnvironment(Environment env, Iterable<BaseFunction> functions) {
1125 for (BaseFunction function : functions) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001126 env.update(function.getName(), function);
1127 }
1128 }
1129
Laurent Le Brun89bdeaa2015-04-14 14:41:59 +00001130 /**
1131 * Collect global functions for the validation environment.
1132 */
Laurent Le Brun352b9da2015-04-16 14:59:59 +00001133 public static void setupValidationEnvironment(Set<String> builtIn) {
Francois-Rene Rideau95b0d0c2015-04-22 16:52:13 +00001134 for (BaseFunction function : skylarkGlobalFunctions) {
Laurent Le Brun352b9da2015-04-16 14:59:59 +00001135 builtIn.add(function.getName());
Laurent Le Brun89bdeaa2015-04-14 14:41:59 +00001136 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001137 }
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001138
1139 static {
1140 SkylarkSignatureProcessor.configureSkylarkFunctions(MethodLibrary.class);
1141 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001142}