blob: e35ecbbc34788fc011460e00fdeb459a4ebc71cf [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
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010017import com.google.common.base.Joiner;
18import com.google.common.collect.ImmutableList;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010019import com.google.common.collect.Iterables;
20import com.google.common.collect.Lists;
Francois-Rene Rideauc673a822015-03-02 19:52:39 +000021import com.google.common.collect.Ordering;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010022import com.google.devtools.build.lib.events.Event;
23import com.google.devtools.build.lib.events.Location;
24import com.google.devtools.build.lib.packages.Type.ConversionException;
Francois-Rene Rideau95b0d0c2015-04-22 16:52:13 +000025import com.google.devtools.build.lib.syntax.BaseFunction;
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +000026import com.google.devtools.build.lib.syntax.BuiltinFunction;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010027import com.google.devtools.build.lib.syntax.ClassObject;
28import com.google.devtools.build.lib.syntax.ClassObject.SkylarkClassObject;
29import com.google.devtools.build.lib.syntax.DotExpression;
30import com.google.devtools.build.lib.syntax.Environment;
31import com.google.devtools.build.lib.syntax.EvalException;
32import com.google.devtools.build.lib.syntax.EvalUtils;
33import com.google.devtools.build.lib.syntax.FuncallExpression;
Greg Estrena4fc8772015-04-13 19:58:23 +000034import com.google.devtools.build.lib.syntax.SelectorList;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010035import com.google.devtools.build.lib.syntax.SelectorValue;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010036import com.google.devtools.build.lib.syntax.SkylarkEnvironment;
37import com.google.devtools.build.lib.syntax.SkylarkList;
38import com.google.devtools.build.lib.syntax.SkylarkModule;
39import com.google.devtools.build.lib.syntax.SkylarkNestedSet;
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +000040import com.google.devtools.build.lib.syntax.SkylarkSignature;
41import com.google.devtools.build.lib.syntax.SkylarkSignature.Param;
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +000042import com.google.devtools.build.lib.syntax.SkylarkSignatureProcessor;
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +000043import com.google.devtools.build.lib.syntax.SkylarkSignatureProcessor.HackHackEitherList;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010044
Googlerc60ec8c2015-03-23 14:20:18 +000045import java.util.ArrayList;
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +000046import java.util.Arrays;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010047import java.util.Collection;
Googlerc60ec8c2015-03-23 14:20:18 +000048import java.util.Iterator;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010049import java.util.List;
50import java.util.Map;
51import java.util.Set;
Laurent Le Brun196c1a72015-03-18 13:03:04 +000052import java.util.TreeMap;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010053import java.util.TreeSet;
54import java.util.concurrent.ExecutionException;
55import java.util.regex.Matcher;
56import java.util.regex.Pattern;
57
58/**
59 * A helper class containing built in functions for the Build and the Build Extension Language.
60 */
61public class MethodLibrary {
62
63 private MethodLibrary() {}
64
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +000065 // TODO(bazel-team):
66 // the Build language and Skylark currently have different list types:
67 // the Build language uses plain java List (usually ArrayList) which is mutable and accepts
68 // any argument, whereas Skylark uses SkylarkList which is immutable and accepts only
69 // arguments of the same kind. Some methods below use HackHackEitherList as a magic marker
70 // to indicate that either kind of lists is used depending on the evaluation context.
71 // It might be a good idea to either have separate methods for the two languages where it matters,
72 // or to unify the two languages so they use the same data structure (which might require
73 // updating all existing clients).
74
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010075 // Convert string index in the same way Python does.
76 // If index is negative, starts from the end.
77 // If index is outside bounds, it is restricted to the valid range.
Francois-Rene Rideau76023b92015-04-17 15:31:59 +000078 private static int clampIndex(int index, int length) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010079 if (index < 0) {
Laurent Le Bruneeef30f2015-03-16 15:12:35 +000080 index += length;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010081 }
Laurent Le Bruneeef30f2015-03-16 15:12:35 +000082 return Math.max(Math.min(index, length), 0);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010083 }
84
85 // Emulate Python substring function
86 // It converts out of range indices, and never fails
Francois-Rene Rideau76023b92015-04-17 15:31:59 +000087 private static String pythonSubstring(String str, int start, Object end, String msg)
88 throws ConversionException {
89 if (start == 0 && EvalUtils.isNullOrNone(end)) {
90 return str;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010091 }
Francois-Rene Rideau76023b92015-04-17 15:31:59 +000092 start = clampIndex(start, str.length());
93 int stop;
94 if (EvalUtils.isNullOrNone(end)) {
95 stop = str.length();
96 } else {
97 stop = clampIndex(Type.INTEGER.convert(end, msg), str.length());
98 }
99 if (start >= stop) {
100 return "";
101 }
102 return str.substring(start, stop);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100103 }
104
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000105 public static int getListIndex(Object key, int listSize, Location loc)
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100106 throws ConversionException, EvalException {
107 // Get the nth element in the list
108 int index = Type.INTEGER.convert(key, "index operand");
109 if (index < 0) {
110 index += listSize;
111 }
112 if (index < 0 || index >= listSize) {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000113 throw new EvalException(loc, "List index out of range (index is "
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100114 + index + ", but list has " + listSize + " elements)");
115 }
116 return index;
117 }
118
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000119 // supported string methods
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100120
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000121 @SkylarkSignature(name = "join", objectType = StringModule.class, returnType = String.class,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100122 doc = "Returns a string in which the string elements of the argument have been "
123 + "joined by this string as a separator. Example:<br>"
Laurent Le Brun9d27a012015-03-31 12:28:02 +0000124 + "<pre class=\"language-python\">\"|\".join([\"a\", \"b\", \"c\"]) == \"a|b|c\"</pre>",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000125 mandatoryPositionals = {
126 @Param(name = "self", type = String.class, doc = "This string, a separator."),
127 @Param(name = "elements", type = HackHackEitherList.class, doc = "The objects to join.")})
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000128 private static BuiltinFunction join = new BuiltinFunction("join") {
129 public String invoke(String self, Object elements) throws ConversionException {
130 List<?> seq = Type.OBJECT_LIST.convert(elements, "'join.elements' argument");
131 return Joiner.on(self).join(seq);
132 }
133 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100134
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000135 @SkylarkSignature(name = "lower", objectType = StringModule.class, returnType = String.class,
136 doc = "Returns the lower case version of this string.",
137 mandatoryPositionals = {
138 @Param(name = "self", type = String.class, doc = "This string, to convert to lower case.")})
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000139 private static BuiltinFunction lower = new BuiltinFunction("lower") {
140 public String invoke(String self) {
141 return self.toLowerCase();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100142 }
143 };
144
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000145 @SkylarkSignature(name = "upper", objectType = StringModule.class, returnType = String.class,
146 doc = "Returns the upper case version of this string.",
147 mandatoryPositionals = {
148 @Param(name = "self", type = String.class, doc = "This string, to convert to upper case.")})
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000149 private static BuiltinFunction upper = new BuiltinFunction("upper") {
150 public String invoke(String self) {
151 return self.toUpperCase();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100152 }
153 };
154
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000155 @SkylarkSignature(name = "replace", objectType = StringModule.class, returnType = String.class,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100156 doc = "Returns a copy of the string in which the occurrences "
157 + "of <code>old</code> have been replaced with <code>new</code>, optionally restricting "
158 + "the number of replacements to <code>maxsplit</code>.",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000159 mandatoryPositionals = {
160 @Param(name = "self", type = String.class, doc = "This string."),
161 @Param(name = "old", type = String.class, doc = "The string to be replaced."),
162 @Param(name = "new", type = String.class, doc = "The string to replace with.")},
163 optionalPositionals = {
Francois-Rene Rideau534dca12015-04-21 19:43:19 +0000164 @Param(name = "maxsplit", type = Integer.class, noneable = true, defaultValue = "None",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000165 doc = "The maximum number of replacements.")},
166 useLocation = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000167 private static BuiltinFunction replace = new BuiltinFunction("replace") {
168 public String invoke(String self, String oldString, String newString, Object maxSplitO,
169 Location loc) throws EvalException, ConversionException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100170 StringBuffer sb = new StringBuffer();
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000171 Integer maxSplit = Type.INTEGER.convertOptional(
172 maxSplitO, "'maxsplit' argument of 'replace'", /*label*/null, Integer.MAX_VALUE);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100173 try {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000174 Matcher m = Pattern.compile(oldString, Pattern.LITERAL).matcher(self);
175 for (int i = 0; i < maxSplit && m.find(); i++) {
176 m.appendReplacement(sb, Matcher.quoteReplacement(newString));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100177 }
178 m.appendTail(sb);
179 } catch (IllegalStateException e) {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000180 throw new EvalException(loc, e.getMessage() + " in call to replace");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100181 }
182 return sb.toString();
183 }
184 };
185
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000186 @SkylarkSignature(name = "split", objectType = StringModule.class,
187 returnType = HackHackEitherList.class,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100188 doc = "Returns a list of all the words in the string, using <code>sep</code> "
189 + "as the separator, optionally limiting the number of splits to <code>maxsplit</code>.",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000190 mandatoryPositionals = {
191 @Param(name = "self", type = String.class, doc = "This string.")},
192 optionalPositionals = {
Francois-Rene Rideau534dca12015-04-21 19:43:19 +0000193 @Param(name = "sep", type = String.class, defaultValue = "' '",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000194 doc = "The string to split on, default is space (\" \")."),
Francois-Rene Rideau534dca12015-04-21 19:43:19 +0000195 @Param(name = "maxsplit", type = Integer.class, noneable = true, defaultValue = "None",
196 doc = "The maximum number of splits.")},
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000197 useEnvironment = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000198 private static BuiltinFunction split = new BuiltinFunction("split") {
199 public Object invoke(String self, String sep, Object maxSplitO,
200 Environment env) throws ConversionException {
201 int maxSplit = Type.INTEGER.convertOptional(
202 maxSplitO, "'split' argument of 'split'", /*label*/null, -2);
203 // + 1 because the last result is the remainder, and default of -2 so that after +1 it's -1
204 String[] ss = Pattern.compile(sep, Pattern.LITERAL).split(self, maxSplit + 1);
205 List<String> result = Arrays.<String>asList(ss);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100206 return env.isSkylarkEnabled() ? SkylarkList.list(result, String.class) : result;
207 }
208 };
Googler7ad2f092015-05-28 11:04:47 +0000209
210 @SkylarkSignature(name = "partition", objectType = StringModule.class,
211 returnType = HackHackEitherList.class,
212 doc = "Splits the input string at the first occurrence of the separator "
213 + "<code>sep</code> and returns the resulting partition as a three-element "
214 + "list of the form [substring_before, separator, substring_after].",
215 mandatoryPositionals = {
216 @Param(name = "self", type = String.class, doc = "This string.")},
217 optionalPositionals = {
218 @Param(name = "sep", type = String.class,
219 defaultValue = "' '", doc = "The string to split on, default is space (\" \").")},
220 useEnvironment = true,
221 useLocation = true)
222 private static BuiltinFunction partition = new BuiltinFunction("partition") {
223 @SuppressWarnings("unused")
224 public Object invoke(String self, String sep, Location loc, Environment env)
225 throws EvalException {
226 return partitionWrapper(self, sep, true, env.isSkylarkEnabled(), loc);
227 }
228 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100229
Googler7ad2f092015-05-28 11:04:47 +0000230 @SkylarkSignature(name = "rpartition", objectType = StringModule.class,
231 returnType = HackHackEitherList.class,
232 doc = "Splits the input string at the last occurrence of the separator "
233 + "<code>sep</code> and returns the resulting partition as a three-element "
234 + "list of the form [substring_before, separator, substring_after].",
235 mandatoryPositionals = {
236 @Param(name = "self", type = String.class, doc = "This string.")},
237 optionalPositionals = {
238 @Param(name = "sep", type = String.class,
239 defaultValue = "' '", doc = "The string to split on, default is space (\" \").")},
240 useEnvironment = true,
241 useLocation = true)
242 private static BuiltinFunction rpartition = new BuiltinFunction("rpartition") {
243 @SuppressWarnings("unused")
244 public Object invoke(String self, String sep, Location loc, Environment env)
245 throws EvalException {
246 return partitionWrapper(self, sep, false, env.isSkylarkEnabled(), loc);
247 }
248 };
249
250 /**
251 * Wraps the stringPartition() method and converts its results and exceptions
252 * to the expected types.
253 *
254 * @param self The input string
255 * @param separator The string to split on
256 * @param forward A flag that controls whether the input string is split around
257 * the first ({@code true}) or last ({@code false}) occurrence of the separator.
258 * @param isSkylarkEnabled Controls whether a SkylarkList ({@code true})or a
259 * {@code java.util.List} ({@code false}) is returned
260 * @param loc The location that is used for potential exceptions
261 * @return A list with three elements
262 */
263 private static Object partitionWrapper(String self, String separator, boolean forward,
264 boolean isSkylarkEnabled, Location loc) throws EvalException {
265 try {
266 List<String> result = stringPartition(self, separator, forward);
267 return isSkylarkEnabled ? SkylarkList.list(result, String.class) : result;
268 } catch (IllegalArgumentException ex) {
269 throw new EvalException(loc, ex);
270 }
271 }
272
273 /**
274 * Splits the input string at the {first|last} occurrence of the given separator
275 * and returns the resulting partition as a three-tuple of Strings, contained
276 * in a {@code List}.
277 *
278 * <p>If the input string does not contain the separator, the tuple will
279 * consist of the original input string and two empty strings.
280 *
281 * <p>This method emulates the behavior of Python's str.partition() and
282 * str.rpartition(), depending on the value of the {@code forward} flag.
283 *
284 * @param input The input string
285 * @param separator The string to split on
286 * @param forward A flag that controls whether the input string is split around
287 * the first ({@code true}) or last ({@code false}) occurrence of the separator.
288 * @return A three-tuple (List) of the form [part_before_separator, separator,
289 * part_after_separator].
290 *
291 */
292 private static List<String> stringPartition(String input, String separator, boolean forward)
293 throws IllegalArgumentException {
294 if (separator.isEmpty()) {
295 throw new IllegalArgumentException("Empty separator");
296 }
297
298 int partitionSize = 3;
299 ArrayList<String> result = new ArrayList<>(partitionSize);
300 int pos = forward ? input.indexOf(separator) : input.lastIndexOf(separator);
301
302 if (pos < 0) {
303 for (int i = 0; i < partitionSize; ++i) {
304 result.add("");
305 }
306
307 // Following Python's implementation of str.partition() and str.rpartition(),
308 // the input string is copied to either the first or the last position in the
309 // list, depending on the value of the forward flag.
310 result.set(forward ? 0 : partitionSize - 1, input);
311 } else {
312 result.add(input.substring(0, pos));
313 result.add(separator);
314
315 // pos + sep.length() is at most equal to input.length(). This worst-case
316 // happens when the separator is at the end of the input string. However,
317 // substring() will return an empty string in this scenario, thus making
318 // any additional safety checks obsolete.
319 result.add(input.substring(pos + separator.length()));
320 }
321
322 return result;
323 }
324
Francois-Rene Rideau76023b92015-04-17 15:31:59 +0000325 /**
326 * Common implementation for find, rfind, index, rindex.
327 * @param forward true if we want to return the last matching index.
328 */
329 private static int stringFind(boolean forward,
330 String self, String sub, int start, Object end, String msg)
331 throws ConversionException {
332 String substr = pythonSubstring(self, start, end, msg);
333 int subpos = forward ? substr.indexOf(sub) : substr.lastIndexOf(sub);
334 start = clampIndex(start, self.length());
335 return subpos < 0 ? subpos : subpos + start;
336 }
337
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000338 @SkylarkSignature(name = "rfind", objectType = StringModule.class, returnType = Integer.class,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100339 doc = "Returns the last index where <code>sub</code> is found, "
340 + "or -1 if no such index exists, optionally restricting to "
341 + "[<code>start</code>:<code>end</code>], "
342 + "<code>start</code> being inclusive and <code>end</code> being exclusive.",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000343 mandatoryPositionals = {
344 @Param(name = "self", type = String.class, doc = "This string."),
345 @Param(name = "sub", type = String.class, doc = "The substring to find.")},
346 optionalPositionals = {
347 @Param(name = "start", type = Integer.class, defaultValue = "0",
348 doc = "Restrict to search from this position."),
349 @Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None",
350 doc = "optional position before which to restrict to search.")})
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000351 private static BuiltinFunction rfind = new BuiltinFunction("rfind") {
352 public Integer invoke(String self, String sub, Integer start, Object end)
353 throws ConversionException {
354 return stringFind(false, self, sub, start, end, "'end' argument to rfind");
355 }
356 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100357
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000358 @SkylarkSignature(name = "find", objectType = StringModule.class, returnType = Integer.class,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100359 doc = "Returns the first index where <code>sub</code> is found, "
360 + "or -1 if no such index exists, optionally restricting to "
361 + "[<code>start</code>:<code>end]</code>, "
362 + "<code>start</code> being inclusive and <code>end</code> being exclusive.",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000363 mandatoryPositionals = {
364 @Param(name = "self", type = String.class, doc = "This string."),
365 @Param(name = "sub", type = String.class, doc = "The substring to find.")},
366 optionalPositionals = {
367 @Param(name = "start", type = Integer.class, defaultValue = "0",
368 doc = "Restrict to search from this position."),
369 @Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None",
370 doc = "optional position before which to restrict to search.")})
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000371 private static BuiltinFunction find = new BuiltinFunction("find") {
372 public Integer invoke(String self, String sub, Integer start, Object end)
373 throws ConversionException {
374 return stringFind(true, self, sub, start, end, "'end' argument to find");
375 }
376 };
Laurent Le Brun4e116c72015-03-23 13:48:50 +0000377
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000378 @SkylarkSignature(name = "rindex", objectType = StringModule.class, returnType = Integer.class,
Laurent Le Brun4e116c72015-03-23 13:48:50 +0000379 doc = "Returns the last index where <code>sub</code> is found, "
380 + "or throw an error if no such index exists, optionally restricting to "
381 + "[<code>start</code>:<code>end</code>], "
382 + "<code>start</code> being inclusive and <code>end</code> being exclusive.",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000383 mandatoryPositionals = {
384 @Param(name = "self", type = String.class, doc = "This string."),
385 @Param(name = "sub", type = String.class, doc = "The substring to find.")},
386 optionalPositionals = {
387 @Param(name = "start", type = Integer.class, defaultValue = "0",
388 doc = "Restrict to search from this position."),
389 @Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None",
390 doc = "optional position before which to restrict to search.")},
391 useLocation = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000392 private static BuiltinFunction rindex = new BuiltinFunction("rindex") {
393 public Integer invoke(String self, String sub, Integer start, Object end,
394 Location loc) throws EvalException, ConversionException {
395 int res = stringFind(false, self, sub, start, end, "'end' argument to rindex");
396 if (res < 0) {
397 throw new EvalException(loc, String.format("substring %s not found in %s",
398 EvalUtils.prettyPrintValue(sub), EvalUtils.prettyPrintValue(self)));
399 }
400 return res;
401 }
402 };
Laurent Le Brun4e116c72015-03-23 13:48:50 +0000403
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000404 @SkylarkSignature(name = "index", objectType = StringModule.class, returnType = Integer.class,
Laurent Le Brun4e116c72015-03-23 13:48:50 +0000405 doc = "Returns the first index where <code>sub</code> is found, "
406 + "or throw an error if no such index exists, optionally restricting to "
407 + "[<code>start</code>:<code>end]</code>, "
408 + "<code>start</code> being inclusive and <code>end</code> being exclusive.",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000409 mandatoryPositionals = {
410 @Param(name = "self", type = String.class, doc = "This string."),
411 @Param(name = "sub", type = String.class, doc = "The substring to find.")},
412 optionalPositionals = {
413 @Param(name = "start", type = Integer.class, defaultValue = "0",
414 doc = "Restrict to search from this position."),
415 @Param(name = "end", type = Integer.class, noneable = true,
416 doc = "optional position before which to restrict to search.")},
417 useLocation = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000418 private static BuiltinFunction index = new BuiltinFunction("index") {
419 public Integer invoke(String self, String sub, Integer start, Object end,
420 Location loc) throws EvalException, ConversionException {
421 int res = stringFind(true, self, sub, start, end, "'end' argument to index");
422 if (res < 0) {
423 throw new EvalException(loc, String.format("substring %s not found in %s",
424 EvalUtils.prettyPrintValue(sub), EvalUtils.prettyPrintValue(self)));
425 }
426 return res;
427 }
428 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100429
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000430 @SkylarkSignature(name = "count", objectType = StringModule.class, returnType = Integer.class,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100431 doc = "Returns the number of (non-overlapping) occurrences of substring <code>sub</code> in "
432 + "string, optionally restricting to [<code>start</code>:<code>end</code>], "
433 + "<code>start</code> being inclusive and <code>end</code> being exclusive.",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000434 mandatoryPositionals = {
435 @Param(name = "self", type = String.class, doc = "This string."),
436 @Param(name = "sub", type = String.class, doc = "The substring to count.")},
437 optionalPositionals = {
438 @Param(name = "start", type = Integer.class, defaultValue = "0",
439 doc = "Restrict to search from this position."),
440 @Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None",
441 doc = "optional position before which to restrict to search.")})
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000442 private static BuiltinFunction count = new BuiltinFunction("count") {
443 public Integer invoke(String self, String sub, Integer start, Object end)
444 throws ConversionException {
445 String str = pythonSubstring(self, start, end, "'end' operand of 'find'");
446 if (sub.isEmpty()) {
447 return str.length() + 1;
448 }
449 int count = 0;
450 int index = -1;
451 while ((index = str.indexOf(sub)) >= 0) {
452 count++;
453 str = str.substring(index + sub.length());
454 }
455 return count;
456 }
457 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100458
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000459 @SkylarkSignature(name = "endswith", objectType = StringModule.class, returnType = Boolean.class,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100460 doc = "Returns True if the string ends with <code>sub</code>, "
461 + "otherwise False, optionally restricting to [<code>start</code>:<code>end</code>], "
462 + "<code>start</code> being inclusive and <code>end</code> being exclusive.",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000463 mandatoryPositionals = {
464 @Param(name = "self", type = String.class, doc = "This string."),
465 @Param(name = "sub", type = String.class, doc = "The substring to check.")},
466 optionalPositionals = {
467 @Param(name = "start", type = Integer.class, defaultValue = "0",
468 doc = "Test beginning at this position."),
469 @Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None",
470 doc = "optional position at which to stop comparing.")})
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000471 private static BuiltinFunction endswith = new BuiltinFunction(
472 "endswith", SkylarkList.tuple(0, Environment.NONE)) {
473 public Boolean invoke(String self, String sub, Integer start, Object end)
474 throws ConversionException {
475 return pythonSubstring(self, start, end, "'end' operand of 'endswith'").endsWith(sub);
476 }
477 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100478
Laurent Le Brunaa83c3a2015-05-08 15:00:35 +0000479 // In Python, formatting is very complex.
480 // We handle here the simplest case which provides most of the value of the function.
481 // https://docs.python.org/3/library/string.html#formatstrings
482 @SkylarkSignature(name = "format", objectType = StringModule.class, returnType = String.class,
483 doc = "Replace the values surrounded by curly brackets in the string."
484 + "<pre class=\"language-python\">"
485 + "\"x{key}x\".format(key = 2) == \"x2x\"</pre>\n",
486 mandatoryPositionals = {
487 @Param(name = "self", type = String.class, doc = "This string."),
488 },
489 extraKeywords = {
490 @Param(name = "kwargs", doc = "the struct fields")},
491 useLocation = true)
492 private static BuiltinFunction format = new BuiltinFunction("format") {
493 public String invoke(String self, Map<String, Object> kwargs, Location loc)
494 throws ConversionException, EvalException {
495 StringBuffer result = new StringBuffer();
496 Pattern pattern = Pattern.compile("\\{[^}]*\\}");
497 Matcher matcher = pattern.matcher(self);
498 while (matcher.find()) {
499 String word = matcher.group();
500 word = word.substring(1, word.length() - 1); // remove the curly braces
501 if (!kwargs.containsKey(word)) {
502 throw new EvalException(loc, "No replacement found for '" + word + "'");
503 }
504 matcher.appendReplacement(result, EvalUtils.printValue(kwargs.get(word)));
505 }
506 matcher.appendTail(result);
507 return result.toString();
508 }
509 };
510
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000511 @SkylarkSignature(name = "startswith", objectType = StringModule.class,
512 returnType = Boolean.class,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100513 doc = "Returns True if the string starts with <code>sub</code>, "
514 + "otherwise False, optionally restricting to [<code>start</code>:<code>end</code>], "
515 + "<code>start</code> being inclusive and <code>end</code> being exclusive.",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000516 mandatoryPositionals = {
517 @Param(name = "self", type = String.class, doc = "This string."),
518 @Param(name = "sub", type = String.class, doc = "The substring to check.")},
519 optionalPositionals = {
520 @Param(name = "start", type = Integer.class, defaultValue = "0",
521 doc = "Test beginning at this position."),
522 @Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None",
523 doc = "Stop comparing at this position.")})
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000524 private static BuiltinFunction startswith = new BuiltinFunction("startswith") {
525 public Boolean invoke(String self, String sub, Integer start, Object end)
526 throws ConversionException {
527 return pythonSubstring(self, start, end, "'end' operand of 'startswith'").startsWith(sub);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100528 }
529 };
530
531 // TODO(bazel-team): Maybe support an argument to tell the type of the whitespace.
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000532 @SkylarkSignature(name = "strip", objectType = StringModule.class, returnType = String.class,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100533 doc = "Returns a copy of the string in which all whitespace characters "
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000534 + "have been stripped from the beginning and the end of the string.",
535 mandatoryPositionals = {
536 @Param(name = "self", type = String.class, doc = "This string.")})
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000537 private static BuiltinFunction strip = new BuiltinFunction("strip") {
538 public String invoke(String self) {
539 return self.trim();
540 }
541 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100542
Laurent Le Bruneeef30f2015-03-16 15:12:35 +0000543 // slice operator
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000544 @SkylarkSignature(name = "$slice", documented = false,
545 mandatoryPositionals = {
546 @Param(name = "self", type = Object.class, doc = "This string, list or tuple."),
547 @Param(name = "start", type = Integer.class, doc = "start position of the slice."),
548 @Param(name = "end", type = Integer.class, doc = "end position of the slice.")},
549 doc = "x[<code>start</code>:<code>end</code>] returns a slice or a list slice.",
550 useLocation = true, useEnvironment = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000551 private static BuiltinFunction slice = new BuiltinFunction("$slice") {
552 public Object invoke(Object self, Integer left, Integer right,
553 Location loc, Environment env) throws EvalException, ConversionException {
Laurent Le Bruneeef30f2015-03-16 15:12:35 +0000554 // Substring
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000555 if (self instanceof String) {
556 return pythonSubstring((String) self, left, right, "");
Laurent Le Bruneeef30f2015-03-16 15:12:35 +0000557 }
558
559 // List slice
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000560 List<Object> list = Type.OBJECT_LIST.convert(self, "list operand");
Francois-Rene Rideau76023b92015-04-17 15:31:59 +0000561 left = clampIndex(left, list.size());
562 right = clampIndex(right, list.size());
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000563 if (left > right) {
564 left = right;
Laurent Le Bruneeef30f2015-03-16 15:12:35 +0000565 }
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000566 return convert(list.subList(left, right), env, loc);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100567 }
568 };
569
570 // supported list methods
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000571 @SkylarkSignature(name = "sorted", returnType = HackHackEitherList.class,
572 doc = "Sort a collection.",
573 mandatoryPositionals = {
574 @Param(name = "self", type = HackHackEitherList.class, doc = "This list.")},
575 useLocation = true, useEnvironment = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000576 private static BuiltinFunction sorted = new BuiltinFunction("sorted") {
577 public Object invoke(Object self, Location loc, Environment env)
Laurent Le Brunef69ec52015-04-16 18:58:34 +0000578 throws EvalException, ConversionException {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000579 List<Object> list = Type.OBJECT_LIST.convert(self, "'sorted' operand");
Laurent Le Brunef69ec52015-04-16 18:58:34 +0000580 try {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000581 list = Ordering.from(EvalUtils.SKYLARK_COMPARATOR).sortedCopy(list);
Laurent Le Brunef69ec52015-04-16 18:58:34 +0000582 } catch (EvalUtils.ComparisonException e) {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000583 throw new EvalException(loc, e);
Laurent Le Brunef69ec52015-04-16 18:58:34 +0000584 }
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000585 return convert(list, env, loc);
Laurent Le Brunef69ec52015-04-16 18:58:34 +0000586 }
587 };
588
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000589 // This function has a SkylarkSignature but is only used by the Build language, not Skylark.
590 @SkylarkSignature(name = "append", returnType = Environment.NoneType.class, documented = false,
591 doc = "Adds an item to the end of the list.",
592 mandatoryPositionals = {
593 // we use List rather than SkylarkList because this is actually for use *outside* Skylark
594 @Param(name = "self", type = List.class, doc = "This list."),
595 @Param(name = "item", type = Object.class, doc = "Item to add at the end.")},
596 useLocation = true, useEnvironment = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000597 private static BuiltinFunction append = new BuiltinFunction("append") {
598 public Environment.NoneType invoke(List<Object> self, Object item,
599 Location loc, Environment env) throws EvalException, ConversionException {
600 self.add(item);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100601 return Environment.NONE;
602 }
603 };
604
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000605 // This function has a SkylarkSignature but is only used by the Build language, not Skylark.
606 @SkylarkSignature(name = "extend", returnType = Environment.NoneType.class, documented = false,
607 doc = "Adds all items to the end of the list.",
608 mandatoryPositionals = {
609 // we use List rather than SkylarkList because this is actually for use *outside* Skylark
610 @Param(name = "self", type = List.class, doc = "This list."),
611 @Param(name = "items", type = List.class, doc = "Items to add at the end.")},
612 useLocation = true, useEnvironment = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000613 private static BuiltinFunction extend = new BuiltinFunction("extend") {
614 public Environment.NoneType invoke(List<Object> self, List<Object> items,
615 Location loc, Environment env) throws EvalException, ConversionException {
616 self.addAll(items);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100617 return Environment.NONE;
618 }
619 };
620
621 // dictionary access operator
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000622 @SkylarkSignature(name = "$index", documented = false,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100623 doc = "Returns the nth element of a list or string, "
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000624 + "or looks up a value in a dictionary.",
625 mandatoryPositionals = {
626 @Param(name = "self", type = Object.class, doc = "This object."),
627 @Param(name = "key", type = Object.class, doc = "The index or key to access.")},
628 useLocation = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000629 private static BuiltinFunction indexOperator = new BuiltinFunction("$index") {
630 public Object invoke(Object self, Object key,
631 Location loc) throws EvalException, ConversionException {
632 if (self instanceof SkylarkList) {
633 SkylarkList list = (SkylarkList) self;
634 if (list.isEmpty()) {
635 throw new EvalException(loc, "List is empty");
636 }
637 int index = getListIndex(key, list.size(), loc);
638 return list.get(index);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100639
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000640 } else if (self instanceof Map<?, ?>) {
641 Map<?, ?> dictionary = (Map<?, ?>) self;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100642 if (!dictionary.containsKey(key)) {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000643 throw new EvalException(loc, String.format("Key %s not found in dictionary",
644 EvalUtils.prettyPrintValue(key)));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100645 }
646 return dictionary.get(key);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100647
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000648 } else if (self instanceof List<?>) {
649 List<Object> list = Type.OBJECT_LIST.convert(self, "index operand");
650 if (list.isEmpty()) {
651 throw new EvalException(loc, "List is empty");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100652 }
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000653 int index = getListIndex(key, list.size(), loc);
654 return list.get(index);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100655
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000656 } else if (self instanceof String) {
657 String str = (String) self;
658 int index = getListIndex(key, str.length(), loc);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100659 return str.substring(index, index + 1);
660
661 } else {
662 // TODO(bazel-team): This is dead code, get rid of it.
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000663 throw new EvalException(loc, String.format(
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100664 "Unsupported datatype (%s) for indexing, only works for dict and list",
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000665 EvalUtils.getDataTypeName(self)));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100666 }
667 }
668 };
669
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000670 @SkylarkSignature(name = "values", objectType = DictModule.class,
671 returnType = HackHackEitherList.class,
Laurent Le Brun9d27a012015-03-31 12:28:02 +0000672 doc = "Return the list of values. Dictionaries are always sorted by their keys:"
673 + "<pre class=\"language-python\">"
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000674 + "{2: \"a\", 4: \"b\", 1: \"c\"}.values() == [\"c\", \"a\", \"b\"]</pre>\n",
675 mandatoryPositionals = {@Param(name = "self", type = Map.class, doc = "This dict.")},
676 useLocation = true, useEnvironment = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000677 private static BuiltinFunction values = new BuiltinFunction("values") {
678 public Object invoke(Map<?, ?> self,
679 Location loc, Environment env) throws EvalException, ConversionException {
Laurent Le Brun196c1a72015-03-18 13:03:04 +0000680 // Use a TreeMap to ensure consistent ordering.
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000681 Map<?, ?> dict = new TreeMap<>(self);
682 return convert(dict.values(), env, loc);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100683 }
684 };
685
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000686 @SkylarkSignature(name = "items", objectType = DictModule.class,
687 returnType = HackHackEitherList.class,
Laurent Le Brun9d27a012015-03-31 12:28:02 +0000688 doc = "Return the list of key-value tuples. Dictionaries are always sorted by their keys:"
689 + "<pre class=\"language-python\">"
690 + "{2: \"a\", 4: \"b\", 1: \"c\"}.items() == [(1, \"c\"), (2, \"a\"), (4, \"b\")]"
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000691 + "</pre>\n",
692 mandatoryPositionals = {
693 @Param(name = "self", type = Map.class, doc = "This dict.")},
694 useLocation = true, useEnvironment = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000695 private static BuiltinFunction items = new BuiltinFunction("items") {
696 public Object invoke(Map<?, ?> self,
697 Location loc, Environment env) throws EvalException, ConversionException {
Laurent Le Brun196c1a72015-03-18 13:03:04 +0000698 // Use a TreeMap to ensure consistent ordering.
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000699 Map<?, ?> dict = new TreeMap<>(self);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100700 List<Object> list = Lists.newArrayListWithCapacity(dict.size());
701 for (Map.Entry<?, ?> entries : dict.entrySet()) {
702 List<?> item = ImmutableList.of(entries.getKey(), entries.getValue());
703 list.add(env.isSkylarkEnabled() ? SkylarkList.tuple(item) : item);
704 }
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000705 return convert(list, env, loc);
706 }
707 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100708
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000709 @SkylarkSignature(name = "keys", objectType = DictModule.class,
710 returnType = HackHackEitherList.class,
Laurent Le Brun9d27a012015-03-31 12:28:02 +0000711 doc = "Return the list of keys. Dictionaries are always sorted by their keys:"
712 + "<pre class=\"language-python\">{2: \"a\", 4: \"b\", 1: \"c\"}.keys() == [1, 2, 4]"
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000713 + "</pre>\n",
714 mandatoryPositionals = {
715 @Param(name = "self", type = Map.class, doc = "This dict.")},
716 useLocation = true, useEnvironment = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000717 // Skylark will only call this on a dict; and
718 // allowed keys are all Comparable... if not mutually, it's OK to get a runtime exception.
719 private static BuiltinFunction keys = new BuiltinFunction("keys") {
720 public Object invoke(Map<Comparable<?>, ?> dict,
721 Location loc, Environment env) throws EvalException {
722 return convert(Ordering.natural().sortedCopy(dict.keySet()), env, loc);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100723 }
724 };
725
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000726 @SkylarkSignature(name = "get", objectType = DictModule.class,
Laurent Le Brunf9148972015-03-31 13:54:36 +0000727 doc = "Return the value for <code>key</code> if <code>key</code> is in the dictionary, "
728 + "else <code>default</code>. If <code>default</code> is not given, it defaults to "
729 + "<code>None</code>, so that this method never throws an error.",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000730 mandatoryPositionals = {
731 @Param(name = "self", doc = "This dict."),
Laurent Le Brunf9148972015-03-31 13:54:36 +0000732 @Param(name = "key", doc = "The key to look for.")},
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000733 optionalPositionals = {
734 @Param(name = "default", defaultValue = "None",
Laurent Le Brunf9148972015-03-31 13:54:36 +0000735 doc = "The default value to use (instead of None) if the key is not found.")})
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000736 private static BuiltinFunction get = new BuiltinFunction("get") {
737 public Object invoke(Map<?, ?> self, Object key, Object defaultValue) {
738 if (self.containsKey(key)) {
739 return self.get(key);
Laurent Le Brunf9148972015-03-31 13:54:36 +0000740 }
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000741 return defaultValue;
Laurent Le Brunf9148972015-03-31 13:54:36 +0000742 }
743 };
744
Laurent Le Bruneeef30f2015-03-16 15:12:35 +0000745 // TODO(bazel-team): Use the same type for both Skylark and BUILD files.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100746 @SuppressWarnings("unchecked")
747 private static Iterable<Object> convert(Collection<?> list, Environment env, Location loc)
748 throws EvalException {
749 if (env.isSkylarkEnabled()) {
750 return SkylarkList.list(list, loc);
751 } else {
752 return Lists.newArrayList(list);
753 }
754 }
755
756 // unary minus
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000757 @SkylarkSignature(name = "-", returnType = Integer.class,
758 documented = false, doc = "Unary minus operator.",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000759 mandatoryPositionals = {
760 @Param(name = "num", type = Integer.class, doc = "The number to negate.")})
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000761 private static BuiltinFunction minus = new BuiltinFunction("-") {
762 public Integer invoke(Integer num) throws ConversionException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100763 return -num;
764 }
765 };
766
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000767 @SkylarkSignature(name = "list", returnType = SkylarkList.class,
Laurent Le Brun9d27a012015-03-31 12:28:02 +0000768 doc = "Converts a collection (e.g. set or dictionary) to a list."
769 + "<pre class=\"language-python\">list([1, 2]) == [1, 2]\n"
770 + "list(set([2, 3, 2])) == [2, 3]\n"
771 + "list({5: \"a\", 2: \"b\", 4: \"c\"}) == [2, 4, 5]</pre>",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000772 mandatoryPositionals = {@Param(name = "x", doc = "The object to convert.")},
773 useLocation = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000774 private static BuiltinFunction list = new BuiltinFunction("list") {
775 public SkylarkList invoke(Object x, Location loc) throws EvalException {
776 return SkylarkList.list(EvalUtils.toCollection(x, loc), loc);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100777 }
778 };
779
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000780 @SkylarkSignature(name = "len", returnType = Integer.class, doc =
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100781 "Returns the length of a string, list, tuple, set, or dictionary.",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000782 mandatoryPositionals = {@Param(name = "x", doc = "The object to check length of.")},
783 useLocation = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000784 private static BuiltinFunction len = new BuiltinFunction("len") {
785 public Integer invoke(Object x, Location loc) throws EvalException {
786 int l = EvalUtils.size(x);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100787 if (l == -1) {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000788 throw new EvalException(loc, EvalUtils.getDataTypeName(x) + " is not iterable");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100789 }
790 return l;
791 }
792 };
793
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000794 @SkylarkSignature(name = "str", returnType = String.class, doc =
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100795 "Converts any object to string. This is useful for debugging.",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000796 mandatoryPositionals = {@Param(name = "x", doc = "The object to convert.")})
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000797 private static BuiltinFunction str = new BuiltinFunction("str") {
798 public String invoke(Object x) throws EvalException {
799 return EvalUtils.printValue(x);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100800 }
801 };
802
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000803 @SkylarkSignature(name = "bool", returnType = Boolean.class,
804 doc = "Converts an object to boolean. "
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100805 + "It returns False if the object is None, False, an empty string, the number 0, or an "
Laurent Le Brun9ba067d2015-05-22 13:55:23 +0000806 + "empty collection. Otherwise, it returns True. As in Python, <code>bool</code> "
Googlerc4cf9132015-03-20 14:54:23 +0000807 + "is also a type.",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000808 mandatoryPositionals = {@Param(name = "x", doc = "The variable to convert.")})
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000809 private static BuiltinFunction bool = new BuiltinFunction("bool") {
810 public Boolean invoke(Object x) throws EvalException {
811 return EvalUtils.toBoolean(x);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100812 }
813 };
814
Laurent Le Brunf4648de2015-05-07 14:00:32 +0000815 @SkylarkSignature(name = "int", returnType = Integer.class, doc = "Converts a value to int. "
816 + "If the argument is a string, it is converted using base 10 and raises an error if the "
817 + "conversion fails. If the argument is a bool, it returns 0 (False) or 1 (True). "
818 + "If the argument is an int, it is simply returned."
Laurent Le Brun0c44aa42015-04-02 11:32:47 +0000819 + "<pre class=\"language-python\">int(\"123\") == 123</pre>",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000820 mandatoryPositionals = {
Laurent Le Brunf4648de2015-05-07 14:00:32 +0000821 @Param(name = "x", type = Object.class, doc = "The string to convert.")},
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000822 useLocation = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000823 private static BuiltinFunction int_ = new BuiltinFunction("int") {
Laurent Le Brunf4648de2015-05-07 14:00:32 +0000824 public Integer invoke(Object x, Location loc) throws EvalException {
825 if (x instanceof Boolean) {
826 return ((Boolean) x).booleanValue() ? 1 : 0;
827 } else if (x instanceof Integer) {
828 return (Integer) x;
829 } else if (x instanceof String) {
830 try {
831 return Integer.parseInt((String) x);
832 } catch (NumberFormatException e) {
833 throw new EvalException(loc,
834 "invalid literal for int(): " + EvalUtils.prettyPrintValue(x));
835 }
836 } else {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000837 throw new EvalException(loc,
Laurent Le Brunf4648de2015-05-07 14:00:32 +0000838 String.format("argument must be string, int, or bool, not '%s'",
839 EvalUtils.prettyPrintValue(x)));
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000840 }
841 }
842 };
Laurent Le Brun0c44aa42015-04-02 11:32:47 +0000843
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000844 @SkylarkSignature(name = "struct", returnType = SkylarkClassObject.class, doc =
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100845 "Creates an immutable struct using the keyword arguments as fields. It is used to group "
846 + "multiple values together.Example:<br>"
Laurent Le Brun9d27a012015-03-31 12:28:02 +0000847 + "<pre class=\"language-python\">s = struct(x = 2, y = 3)\n"
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000848 + "return s.x + s.y # returns 5</pre>",
849 extraKeywords = {
Laurent Le Brunaa83c3a2015-05-08 15:00:35 +0000850 @Param(name = "kwargs", doc = "the struct fields")},
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000851 useLocation = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000852 private static BuiltinFunction struct = new BuiltinFunction("struct") {
853 @SuppressWarnings("unchecked")
854 public SkylarkClassObject invoke(Map<String, Object> kwargs, Location loc)
855 throws EvalException, InterruptedException {
856 return new SkylarkClassObject(kwargs, loc);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100857 }
858 };
859
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000860 @SkylarkSignature(name = "set", returnType = SkylarkNestedSet.class,
Laurent Le Brun7ace1f22015-04-09 13:54:21 +0000861 doc = "Creates a <a href=\"#modules.set\">set</a> from the <code>items</code>."
862 + " The set supports nesting other sets of the same element"
863 + " type in it. A desired iteration order can also be specified.<br>"
864 + " Examples:<br><pre class=\"language-python\">set([\"a\", \"b\"])\n"
Laszlo Csomoraded1c22015-02-16 16:57:12 +0000865 + "set([1, 2, 3], order=\"compile\")</pre>",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000866 optionalPositionals = {
867 @Param(name = "items", type = Object.class, defaultValue = "[]",
868 doc = "The items to initialize the set with. May contain both standalone items "
869 + "and other sets."),
870 @Param(name = "order", type = String.class, defaultValue = "\"stable\"",
871 doc = "The ordering strategy for the set if it's nested, "
872 + "possible values are: <code>stable</code> (default), <code>compile</code>, "
873 + "<code>link</code> or <code>naive_link</code>.")},
874 useLocation = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000875 private static final BuiltinFunction set = new BuiltinFunction("set") {
876 public SkylarkNestedSet invoke(Object items, String order,
877 Location loc) throws EvalException, ConversionException {
878 return new SkylarkNestedSet(SkylarkNestedSet.parseOrder(order, loc), items, loc);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100879 }
880 };
881
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000882 @SkylarkSignature(name = "enumerate", returnType = SkylarkList.class,
Laurent Le Brun9ba067d2015-05-22 13:55:23 +0000883 doc = "Return a list of pairs (two-element tuples), with the index (int) and the item from"
Laurent Le Brun9d27a012015-03-31 12:28:02 +0000884 + " the input list.\n<pre class=\"language-python\">"
Laurent Le Brun9ba067d2015-05-22 13:55:23 +0000885 + "enumerate([24, 21, 84]) == [(0, 24), (1, 21), (2, 84)]</pre>\n",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000886 mandatoryPositionals = {@Param(name = "list", type = SkylarkList.class, doc = "input list")},
887 useLocation = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000888 private static BuiltinFunction enumerate = new BuiltinFunction("enumerate") {
889 public SkylarkList invoke(SkylarkList input, Location loc)
890 throws EvalException, ConversionException, InterruptedException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100891 int count = 0;
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000892 List<SkylarkList> result = Lists.newArrayList();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100893 for (Object obj : input) {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000894 result.add(SkylarkList.tuple(count, obj));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100895 count++;
896 }
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000897 return SkylarkList.list(result, loc);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100898 }
899 };
900
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000901 @SkylarkSignature(name = "range", returnType = SkylarkList.class,
Laszlo Csomor52fb6a22015-02-17 09:46:49 +0000902 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 +0100903 + "<code>step</code> increment. If a single argument is provided, items will "
904 + "range from 0 to that element."
Laurent Le Brun9d27a012015-03-31 12:28:02 +0000905 + "<pre class=\"language-python\">range(4) == [0, 1, 2, 3]\n"
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100906 + "range(3, 9, 2) == [3, 5, 7]\n"
907 + "range(3, 0, -1) == [3, 2, 1]</pre>",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000908 mandatoryPositionals = {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000909 @Param(name = "start_or_stop", type = Integer.class,
910 doc = "Value of the start element if stop is provided, "
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000911 + "otherwise value of stop and the actual start is 0"),
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100912 },
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000913 optionalPositionals = {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000914 @Param(name = "stop_or_none", type = Integer.class, noneable = true, defaultValue = "None",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000915 doc = "optional index of the first item <i>not</i> to be included in the "
916 + "resulting list; generation of the list stops before <code>stop</code> is reached."),
Francois-Rene Rideau534dca12015-04-21 19:43:19 +0000917 @Param(name = "step", type = Integer.class, defaultValue = "1",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000918 doc = "The increment (default is 1). It may be negative.")},
919 useLocation = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000920 private static final BuiltinFunction range = new BuiltinFunction("range") {
921 public SkylarkList invoke(Integer startOrStop, Object stopOrNone, Integer step, Location loc)
922 throws EvalException, ConversionException, InterruptedException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100923 int start;
924 int stop;
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000925 if (stopOrNone == Environment.NONE) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100926 start = 0;
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000927 stop = startOrStop;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100928 } else {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000929 start = startOrStop;
930 stop = Type.INTEGER.convert(stopOrNone, "'stop' operand of 'range'");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100931 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100932 if (step == 0) {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000933 throw new EvalException(loc, "step cannot be 0");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100934 }
935 List<Integer> result = Lists.newArrayList();
936 if (step > 0) {
937 while (start < stop) {
938 result.add(start);
939 start += step;
940 }
941 } else {
942 while (start > stop) {
943 result.add(start);
944 start += step;
945 }
946 }
947 return SkylarkList.list(result, Integer.class);
948 }
949 };
950
951 /**
952 * Returns a function-value implementing "select" (i.e. configurable attributes)
953 * in the specified package context.
954 */
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000955 @SkylarkSignature(name = "select",
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100956 doc = "Creates a SelectorValue from the dict parameter.",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000957 mandatoryPositionals = {
958 @Param(name = "x", type = Map.class, doc = "The parameter to convert.")})
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000959 private static final BuiltinFunction select = new BuiltinFunction("select") {
960 public Object invoke(Map<?, ?> dict) throws EvalException, InterruptedException {
961 return SelectorList.of(new SelectorValue(dict));
962 }
963 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100964
965 /**
966 * Returns true if the object has a field of the given name, otherwise false.
967 */
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000968 @SkylarkSignature(name = "hasattr", returnType = Boolean.class,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100969 doc = "Returns True if the object <code>x</code> has a field of the given <code>name</code>, "
970 + "otherwise False. Example:<br>"
Laurent Le Brun9d27a012015-03-31 12:28:02 +0000971 + "<pre class=\"language-python\">hasattr(ctx.attr, \"myattr\")</pre>",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000972 mandatoryPositionals = {
973 @Param(name = "object", doc = "The object to check."),
974 @Param(name = "name", type = String.class, doc = "The name of the field.")},
975 useLocation = true, useEnvironment = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000976 private static final BuiltinFunction hasattr = new BuiltinFunction("hasattr") {
977 public Boolean invoke(Object obj, String name,
978 Location loc, Environment env) throws EvalException, ConversionException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100979 if (obj instanceof ClassObject && ((ClassObject) obj).getValue(name) != null) {
980 return true;
981 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100982 if (env.getFunctionNames(obj.getClass()).contains(name)) {
983 return true;
984 }
985
986 try {
987 return FuncallExpression.getMethodNames(obj.getClass()).contains(name);
988 } catch (ExecutionException e) {
989 // This shouldn't happen
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000990 throw new EvalException(loc, e.getMessage());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100991 }
992 }
993 };
994
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000995 @SkylarkSignature(name = "getattr",
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100996 doc = "Returns the struct's field of the given name if exists, otherwise <code>default</code>"
Laurent Le Brun9ba067d2015-05-22 13:55:23 +0000997 + " if specified, otherwise raises an error. For example, <code>getattr(x, \"foobar\")"
998 + "</code> is equivalent to <code>x.foobar</code>, except that it returns "
999 + "<code>default</code> for a non-existant attribute instead of raising an error."
1000 + "<br>"
Laurent Le Brun9d27a012015-03-31 12:28:02 +00001001 + "<pre class=\"language-python\">getattr(ctx.attr, \"myattr\")\n"
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001002 + "getattr(ctx.attr, \"myattr\", \"mydefault\")</pre>",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001003 mandatoryPositionals = {
Laurent Le Brun9ba067d2015-05-22 13:55:23 +00001004 @Param(name = "object", doc = "The struct whose field is accessed."),
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001005 @Param(name = "name", doc = "The name of the struct field.")},
1006 optionalPositionals = {
Francois-Rene Rideau534dca12015-04-21 19:43:19 +00001007 @Param(name = "default", defaultValue = "None",
1008 doc = "The default value to return in case the struct "
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001009 + "doesn't have a field of the given name.")},
1010 useLocation = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001011 private static final BuiltinFunction getattr = new BuiltinFunction("getattr") {
1012 public Object invoke(Object obj, String name, Object defaultValue,
1013 Location loc) throws EvalException, ConversionException {
1014 Object result = DotExpression.eval(obj, name, loc);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001015 if (result == null) {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001016 if (defaultValue != Environment.NONE) {
1017 return defaultValue;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001018 } else {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001019 throw new EvalException(loc, String.format("Object of type '%s' has no field %s",
Francois-Rene Rideau534dca12015-04-21 19:43:19 +00001020 EvalUtils.getDataTypeName(obj), EvalUtils.prettyPrintValue(name)));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001021 }
1022 }
1023 return result;
1024 }
1025 };
1026
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001027 @SkylarkSignature(name = "dir", returnType = SkylarkList.class,
Laszlo Csomoraded1c22015-02-16 16:57:12 +00001028 doc = "Returns a list strings: the names of the fields and "
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001029 + "methods of the parameter object.",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001030 mandatoryPositionals = {@Param(name = "object", doc = "The object to check.")},
1031 useLocation = true, useEnvironment = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001032 private static final BuiltinFunction dir = new BuiltinFunction("dir") {
1033 public SkylarkList invoke(Object object,
1034 Location loc, Environment env) throws EvalException, ConversionException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001035 // Order the fields alphabetically.
1036 Set<String> fields = new TreeSet<>();
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001037 if (object instanceof ClassObject) {
1038 fields.addAll(((ClassObject) object).getKeys());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001039 }
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001040 fields.addAll(env.getFunctionNames(object.getClass()));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001041 try {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001042 fields.addAll(FuncallExpression.getMethodNames(object.getClass()));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001043 } catch (ExecutionException e) {
1044 // This shouldn't happen
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001045 throw new EvalException(loc, e.getMessage());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001046 }
1047 return SkylarkList.list(fields, String.class);
1048 }
1049 };
1050
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001051 @SkylarkSignature(name = "type", returnType = String.class,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001052 doc = "Returns the type name of its argument.",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001053 mandatoryPositionals = {@Param(name = "object", doc = "The object to check type of.")})
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001054 private static final BuiltinFunction type = new BuiltinFunction("type") {
1055 public String invoke(Object object) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001056 // There is no 'type' type in Skylark, so we return a string with the type name.
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001057 return EvalUtils.getDataTypeName(object, false);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001058 }
1059 };
1060
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001061 @SkylarkSignature(name = "fail",
Laurent Le Brun59fc7f32015-04-20 14:02:40 +00001062 doc = "Raises an error that cannot be intercepted.",
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001063 returnType = Environment.NoneType.class,
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001064 mandatoryPositionals = {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001065 @Param(name = "msg", type = String.class, doc = "Error message to display for the user")},
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001066 optionalPositionals = {
1067 @Param(name = "attr", type = String.class, noneable = true,
1068 defaultValue = "None",
1069 doc = "The name of the attribute that caused the error")},
1070 useLocation = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001071 private static final BuiltinFunction fail = new BuiltinFunction("fail") {
1072 public Environment.NoneType invoke(String msg, Object attr,
1073 Location loc) throws EvalException, ConversionException {
1074 if (attr != Environment.NONE) {
1075 msg = String.format("attribute %s: %s", attr, msg);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001076 }
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001077 throw new EvalException(loc, msg);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001078 }
1079 };
1080
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001081 @SkylarkSignature(name = "print", returnType = Environment.NoneType.class,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001082 doc = "Prints <code>msg</code> to the console.",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001083 optionalNamedOnly = {
Francois-Rene Rideau534dca12015-04-21 19:43:19 +00001084 @Param(name = "sep", type = String.class, defaultValue = "' '",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001085 doc = "The separator string between the objects, default is space (\" \").")},
1086 // NB: as compared to Python3, we're missing optional named-only arguments 'end' and 'file'
1087 extraPositionals = {@Param(name = "args", doc = "The objects to print.")},
1088 useLocation = true, useEnvironment = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001089 private static final BuiltinFunction print = new BuiltinFunction("print") {
1090 public Environment.NoneType invoke(String sep, SkylarkList starargs,
1091 Location loc, SkylarkEnvironment env) throws EvalException {
1092 String msg = Joiner.on(sep).join(Iterables.transform(starargs,
1093 new com.google.common.base.Function<Object, String>() {
1094 @Override
1095 public String apply(Object input) {
1096 return EvalUtils.printValue(input);
1097 }}));
1098 env.handleEvent(Event.warn(loc, msg));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001099 return Environment.NONE;
1100 }
1101 };
1102
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001103 @SkylarkSignature(name = "zip",
Googlerc60ec8c2015-03-23 14:20:18 +00001104 doc = "Returns a <code>list</code> of <code>tuple</code>s, where the i-th tuple contains "
1105 + "the i-th element from each of the argument sequences or iterables. The list has the "
1106 + "size of the shortest input. With a single iterable argument, it returns a list of "
1107 + "1-tuples. With no arguments, it returns an empty list. Examples:"
Laurent Le Brun9d27a012015-03-31 12:28:02 +00001108 + "<pre class=\"language-python\">"
Googlerc60ec8c2015-03-23 14:20:18 +00001109 + "zip() # == []\n"
1110 + "zip([1, 2]) # == [(1,), (2,)]\n"
1111 + "zip([1, 2], [3, 4]) # == [(1, 3), (2, 4)]\n"
1112 + "zip([1, 2], [3, 4, 5]) # == [(1, 3), (2, 4)]</pre>",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001113 extraPositionals = {@Param(name = "args", doc = "lists to zip")},
1114 returnType = SkylarkList.class, useLocation = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001115 private static final BuiltinFunction zip = new BuiltinFunction("zip") {
1116 public SkylarkList invoke(SkylarkList args, Location loc)
1117 throws EvalException, InterruptedException {
Googlerc60ec8c2015-03-23 14:20:18 +00001118 Iterator<?>[] iterators = new Iterator<?>[args.size()];
1119 for (int i = 0; i < args.size(); i++) {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001120 iterators[i] = EvalUtils.toIterable(args.get(i), loc).iterator();
Googlerc60ec8c2015-03-23 14:20:18 +00001121 }
1122 List<SkylarkList> result = new ArrayList<SkylarkList>();
1123 boolean allHasNext;
1124 do {
1125 allHasNext = !args.isEmpty();
1126 List<Object> elem = Lists.newArrayListWithExpectedSize(args.size());
1127 for (Iterator<?> iterator : iterators) {
1128 if (iterator.hasNext()) {
1129 elem.add(iterator.next());
1130 } else {
1131 allHasNext = false;
1132 }
1133 }
1134 if (allHasNext) {
1135 result.add(SkylarkList.tuple(elem));
1136 }
1137 } while (allHasNext);
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001138 return SkylarkList.list(result, loc);
Googlerc60ec8c2015-03-23 14:20:18 +00001139 }
1140 };
1141
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001142 /**
1143 * Skylark String module.
1144 */
1145 @SkylarkModule(name = "string", doc =
1146 "A language built-in type to support strings. "
Laurent Le Brun4848a652015-03-18 14:50:12 +00001147 + "Examples of string literals:<br>"
Laurent Le Brun9d27a012015-03-31 12:28:02 +00001148 + "<pre class=\"language-python\">a = 'abc\\ndef'\n"
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001149 + "b = \"ab'cd\"\n"
Laurent Le Brun4848a652015-03-18 14:50:12 +00001150 + "c = \"\"\"multiline string\"\"\"\n"
1151 + "\n"
1152 + "# Strings support slicing (negative index starts from the end):\n"
1153 + "x = \"hello\"[2:4] # \"ll\"\n"
1154 + "y = \"hello\"[1:-1] # \"ell\"\n"
1155 + "z = \"hello\"[:4] # \"hell\"</pre>"
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001156 + "Strings are iterable and support the <code>in</code> operator. Examples:<br>"
Laurent Le Brun9d27a012015-03-31 12:28:02 +00001157 + "<pre class=\"language-python\">\"a\" in \"abc\" # evaluates as True\n"
Laszlo Csomoraded1c22015-02-16 16:57:12 +00001158 + "x = []\n"
Laurent Le Brun9ba067d2015-05-22 13:55:23 +00001159 + "for s in \"abc\":\n"
1160 + " x += [s] # x == [\"a\", \"b\", \"c\"]</pre>\n"
1161 + "Implicit concatenation of strings is not allowed; use the <code>+</code> "
1162 + "operator instead.")
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001163 public static final class StringModule {}
1164
1165 /**
1166 * Skylark Dict module.
1167 */
1168 @SkylarkModule(name = "dict", doc =
1169 "A language built-in type to support dicts. "
1170 + "Example of dict literal:<br>"
Laurent Le Brun9d27a012015-03-31 12:28:02 +00001171 + "<pre class=\"language-python\">d = {\"a\": 2, \"b\": 5}</pre>"
1172 + "Use brackets to access elements:<br>"
1173 + "<pre class=\"language-python\">e = d[\"a\"] # e == 2</pre>"
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001174 + "Dicts support the <code>+</code> operator to concatenate two dicts. In case of multiple "
1175 + "keys the second one overrides the first one. Examples:<br>"
Laurent Le Brun9d27a012015-03-31 12:28:02 +00001176 + "<pre class=\"language-python\">"
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001177 + "d = {\"a\" : 1} + {\"b\" : 2} # d == {\"a\" : 1, \"b\" : 2}\n"
1178 + "d += {\"c\" : 3} # d == {\"a\" : 1, \"b\" : 2, \"c\" : 3}\n"
1179 + "d = d + {\"c\" : 5} # d == {\"a\" : 1, \"b\" : 2, \"c\" : 5}</pre>"
1180 + "Since the language doesn't have mutable objects <code>d[\"a\"] = 5</code> automatically "
1181 + "translates to <code>d = d + {\"a\" : 5}</code>.<br>"
Laurent Le Brun9d27a012015-03-31 12:28:02 +00001182 + "Iterating on a dict is equivalent to iterating on its keys (in sorted order).<br>"
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001183 + "Dicts support the <code>in</code> operator, testing membership in the keyset of the dict. "
1184 + "Example:<br>"
Laurent Le Brun9d27a012015-03-31 12:28:02 +00001185 + "<pre class=\"language-python\">\"a\" in {\"a\" : 2, \"b\" : 5} # evaluates as True"
1186 + "</pre>")
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001187 public static final class DictModule {}
1188
Francois-Rene Rideau95b0d0c2015-04-22 16:52:13 +00001189 public static final List<BaseFunction> stringFunctions = ImmutableList.<BaseFunction>of(
Googler7ad2f092015-05-28 11:04:47 +00001190 count, endswith, find, index, format, join, lower, partition, replace, rfind,
1191 rindex, rpartition, slice, split, startswith, strip, upper);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001192
Francois-Rene Rideau95b0d0c2015-04-22 16:52:13 +00001193 public static final List<BaseFunction> listPureFunctions = ImmutableList.<BaseFunction>of(
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001194 slice);
Laurent Le Bruneeef30f2015-03-16 15:12:35 +00001195
Francois-Rene Rideau95b0d0c2015-04-22 16:52:13 +00001196 public static final List<BaseFunction> listFunctions = ImmutableList.<BaseFunction>of(
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001197 append, extend);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001198
Francois-Rene Rideau95b0d0c2015-04-22 16:52:13 +00001199 public static final List<BaseFunction> dictFunctions = ImmutableList.<BaseFunction>of(
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001200 items, get, keys, values);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001201
Francois-Rene Rideau95b0d0c2015-04-22 16:52:13 +00001202 private static final List<BaseFunction> pureGlobalFunctions = ImmutableList.<BaseFunction>of(
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001203 bool, int_, len, minus, select, sorted, str);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001204
Francois-Rene Rideau95b0d0c2015-04-22 16:52:13 +00001205 private static final List<BaseFunction> skylarkGlobalFunctions =
1206 ImmutableList.<BaseFunction>builder()
Laurent Le Brun352b9da2015-04-16 14:59:59 +00001207 .addAll(pureGlobalFunctions)
Francois-Rene Rideau534dca12015-04-21 19:43:19 +00001208 .add(list, struct, hasattr, getattr, set, dir, enumerate, range, type, fail, print, zip)
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001209 .build();
1210
1211 /**
1212 * Set up a given environment for supported class methods.
1213 */
1214 public static void setupMethodEnvironment(Environment env) {
Laurent Le Brun4e116c72015-03-23 13:48:50 +00001215 env.registerFunction(Map.class, indexOperator.getName(), indexOperator);
Laurent Le Brun89bdeaa2015-04-14 14:41:59 +00001216 setupMethodEnvironment(env, Map.class, dictFunctions);
Laurent Le Brun4e116c72015-03-23 13:48:50 +00001217 env.registerFunction(String.class, indexOperator.getName(), indexOperator);
Laurent Le Brun89bdeaa2015-04-14 14:41:59 +00001218 setupMethodEnvironment(env, String.class, stringFunctions);
1219 setupMethodEnvironment(env, List.class, listPureFunctions);
1220 setupMethodEnvironment(env, SkylarkList.class, listPureFunctions);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001221 if (env.isSkylarkEnabled()) {
Laurent Le Brun4e116c72015-03-23 13:48:50 +00001222 env.registerFunction(SkylarkList.class, indexOperator.getName(), indexOperator);
Laurent Le Brun352b9da2015-04-16 14:59:59 +00001223 setupMethodEnvironment(env, skylarkGlobalFunctions);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001224 } else {
Laurent Le Brun4e116c72015-03-23 13:48:50 +00001225 env.registerFunction(List.class, indexOperator.getName(), indexOperator);
1226 env.registerFunction(ImmutableList.class, indexOperator.getName(), indexOperator);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001227 // TODO(bazel-team): listFunctions are not allowed in Skylark extensions (use += instead).
1228 // It is allowed in BUILD files only for backward-compatibility.
1229 setupMethodEnvironment(env, List.class, listFunctions);
Laurent Le Brun352b9da2015-04-16 14:59:59 +00001230 setupMethodEnvironment(env, pureGlobalFunctions);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001231 }
1232 }
1233
1234 private static void setupMethodEnvironment(
Francois-Rene Rideau95b0d0c2015-04-22 16:52:13 +00001235 Environment env, Class<?> nameSpace, Iterable<BaseFunction> functions) {
1236 for (BaseFunction function : functions) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001237 env.registerFunction(nameSpace, function.getName(), function);
1238 }
1239 }
1240
Francois-Rene Rideau95b0d0c2015-04-22 16:52:13 +00001241 private static void setupMethodEnvironment(Environment env, Iterable<BaseFunction> functions) {
1242 for (BaseFunction function : functions) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001243 env.update(function.getName(), function);
1244 }
1245 }
1246
Laurent Le Brun89bdeaa2015-04-14 14:41:59 +00001247 /**
1248 * Collect global functions for the validation environment.
1249 */
Laurent Le Brun352b9da2015-04-16 14:59:59 +00001250 public static void setupValidationEnvironment(Set<String> builtIn) {
Francois-Rene Rideau95b0d0c2015-04-22 16:52:13 +00001251 for (BaseFunction function : skylarkGlobalFunctions) {
Laurent Le Brun352b9da2015-04-16 14:59:59 +00001252 builtIn.add(function.getName());
Laurent Le Brun89bdeaa2015-04-14 14:41:59 +00001253 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001254 }
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001255
1256 static {
1257 SkylarkSignatureProcessor.configureSkylarkFunctions(MethodLibrary.class);
1258 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001259}