blob: ef958d356f3383f497d189b28469469570e4a57b [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
Francois-Rene Rideau36795562015-07-29 18:50:50 +000015package com.google.devtools.build.lib.syntax;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010016
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;
Florian Weikertfa759e22015-06-09 12:18:46 +000022import com.google.devtools.build.lib.collect.nestedset.Order;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010023import com.google.devtools.build.lib.events.Event;
24import com.google.devtools.build.lib.events.Location;
Francois-Rene Rideau36795562015-07-29 18:50:50 +000025import com.google.devtools.build.lib.packages.Type;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010026import com.google.devtools.build.lib.packages.Type.ConversionException;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010027import com.google.devtools.build.lib.syntax.ClassObject.SkylarkClassObject;
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +000028import com.google.devtools.build.lib.syntax.SkylarkSignature.Param;
29import com.google.devtools.build.lib.syntax.SkylarkSignatureProcessor.HackHackEitherList;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010030
Googlerc60ec8c2015-03-23 14:20:18 +000031import java.util.ArrayList;
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +000032import java.util.Arrays;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010033import java.util.Collection;
Florian Weikerta6dae6b2015-08-04 20:17:23 +000034import java.util.HashMap;
Googlerc60ec8c2015-03-23 14:20:18 +000035import java.util.Iterator;
Florian Weikert8e2e5fc2015-05-29 12:39:08 +000036import java.util.LinkedList;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010037import java.util.List;
38import java.util.Map;
39import java.util.Set;
Laurent Le Brun196c1a72015-03-18 13:03:04 +000040import java.util.TreeMap;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010041import java.util.TreeSet;
42import java.util.concurrent.ExecutionException;
43import java.util.regex.Matcher;
44import java.util.regex.Pattern;
45
46/**
47 * A helper class containing built in functions for the Build and the Build Extension Language.
48 */
49public class MethodLibrary {
50
51 private MethodLibrary() {}
52
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +000053 // TODO(bazel-team):
54 // the Build language and Skylark currently have different list types:
55 // the Build language uses plain java List (usually ArrayList) which is mutable and accepts
56 // any argument, whereas Skylark uses SkylarkList which is immutable and accepts only
57 // arguments of the same kind. Some methods below use HackHackEitherList as a magic marker
58 // to indicate that either kind of lists is used depending on the evaluation context.
59 // It might be a good idea to either have separate methods for the two languages where it matters,
60 // or to unify the two languages so they use the same data structure (which might require
61 // updating all existing clients).
62
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010063 // Convert string index in the same way Python does.
64 // If index is negative, starts from the end.
65 // If index is outside bounds, it is restricted to the valid range.
Francois-Rene Rideau76023b92015-04-17 15:31:59 +000066 private static int clampIndex(int index, int length) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010067 if (index < 0) {
Laurent Le Bruneeef30f2015-03-16 15:12:35 +000068 index += length;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010069 }
Laurent Le Bruneeef30f2015-03-16 15:12:35 +000070 return Math.max(Math.min(index, length), 0);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010071 }
72
73 // Emulate Python substring function
74 // It converts out of range indices, and never fails
Francois-Rene Rideau76023b92015-04-17 15:31:59 +000075 private static String pythonSubstring(String str, int start, Object end, String msg)
76 throws ConversionException {
77 if (start == 0 && EvalUtils.isNullOrNone(end)) {
78 return str;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010079 }
Francois-Rene Rideau76023b92015-04-17 15:31:59 +000080 start = clampIndex(start, str.length());
81 int stop;
82 if (EvalUtils.isNullOrNone(end)) {
83 stop = str.length();
84 } else {
85 stop = clampIndex(Type.INTEGER.convert(end, msg), str.length());
86 }
87 if (start >= stop) {
88 return "";
89 }
90 return str.substring(start, stop);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010091 }
92
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +000093 private static int getListIndex(Object key, int listSize, Location loc)
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010094 throws ConversionException, EvalException {
95 // Get the nth element in the list
96 int index = Type.INTEGER.convert(key, "index operand");
97 if (index < 0) {
98 index += listSize;
99 }
100 if (index < 0 || index >= listSize) {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000101 throw new EvalException(loc, "List index out of range (index is "
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100102 + index + ", but list has " + listSize + " elements)");
103 }
104 return index;
105 }
106
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000107 // supported string methods
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100108
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000109 @SkylarkSignature(name = "join", objectType = StringModule.class, returnType = String.class,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100110 doc = "Returns a string in which the string elements of the argument have been "
111 + "joined by this string as a separator. Example:<br>"
Laurent Le Brun9d27a012015-03-31 12:28:02 +0000112 + "<pre class=\"language-python\">\"|\".join([\"a\", \"b\", \"c\"]) == \"a|b|c\"</pre>",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000113 mandatoryPositionals = {
114 @Param(name = "self", type = String.class, doc = "This string, a separator."),
115 @Param(name = "elements", type = HackHackEitherList.class, doc = "The objects to join.")})
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000116 private static BuiltinFunction join = new BuiltinFunction("join") {
117 public String invoke(String self, Object elements) throws ConversionException {
118 List<?> seq = Type.OBJECT_LIST.convert(elements, "'join.elements' argument");
119 return Joiner.on(self).join(seq);
120 }
121 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100122
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000123 @SkylarkSignature(name = "lower", objectType = StringModule.class, returnType = String.class,
124 doc = "Returns the lower case version of this string.",
125 mandatoryPositionals = {
126 @Param(name = "self", type = String.class, doc = "This string, to convert to lower case.")})
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000127 private static BuiltinFunction lower = new BuiltinFunction("lower") {
128 public String invoke(String self) {
129 return self.toLowerCase();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100130 }
131 };
132
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000133 @SkylarkSignature(name = "upper", objectType = StringModule.class, returnType = String.class,
134 doc = "Returns the upper case version of this string.",
135 mandatoryPositionals = {
136 @Param(name = "self", type = String.class, doc = "This string, to convert to upper case.")})
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000137 private static BuiltinFunction upper = new BuiltinFunction("upper") {
138 public String invoke(String self) {
139 return self.toUpperCase();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100140 }
141 };
142
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000143 @SkylarkSignature(name = "replace", objectType = StringModule.class, returnType = String.class,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100144 doc = "Returns a copy of the string in which the occurrences "
145 + "of <code>old</code> have been replaced with <code>new</code>, optionally restricting "
146 + "the number of replacements to <code>maxsplit</code>.",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000147 mandatoryPositionals = {
148 @Param(name = "self", type = String.class, doc = "This string."),
149 @Param(name = "old", type = String.class, doc = "The string to be replaced."),
150 @Param(name = "new", type = String.class, doc = "The string to replace with.")},
151 optionalPositionals = {
Francois-Rene Rideau534dca12015-04-21 19:43:19 +0000152 @Param(name = "maxsplit", type = Integer.class, noneable = true, defaultValue = "None",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000153 doc = "The maximum number of replacements.")},
154 useLocation = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000155 private static BuiltinFunction replace = new BuiltinFunction("replace") {
156 public String invoke(String self, String oldString, String newString, Object maxSplitO,
157 Location loc) throws EvalException, ConversionException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100158 StringBuffer sb = new StringBuffer();
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000159 Integer maxSplit = Type.INTEGER.convertOptional(
160 maxSplitO, "'maxsplit' argument of 'replace'", /*label*/null, Integer.MAX_VALUE);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100161 try {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000162 Matcher m = Pattern.compile(oldString, Pattern.LITERAL).matcher(self);
163 for (int i = 0; i < maxSplit && m.find(); i++) {
164 m.appendReplacement(sb, Matcher.quoteReplacement(newString));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100165 }
166 m.appendTail(sb);
167 } catch (IllegalStateException e) {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000168 throw new EvalException(loc, e.getMessage() + " in call to replace");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100169 }
170 return sb.toString();
171 }
172 };
173
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000174 @SkylarkSignature(name = "split", objectType = StringModule.class,
175 returnType = HackHackEitherList.class,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100176 doc = "Returns a list of all the words in the string, using <code>sep</code> "
177 + "as the separator, optionally limiting the number of splits to <code>maxsplit</code>.",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000178 mandatoryPositionals = {
Florian Weikert8e2e5fc2015-05-29 12:39:08 +0000179 @Param(name = "self", type = String.class, doc = "This string."),
180 @Param(name = "sep", type = String.class, doc = "The string to split on.")},
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000181 optionalPositionals = {
Francois-Rene Rideau534dca12015-04-21 19:43:19 +0000182 @Param(name = "maxsplit", type = Integer.class, noneable = true, defaultValue = "None",
183 doc = "The maximum number of splits.")},
Florian Weikert8e2e5fc2015-05-29 12:39:08 +0000184 useEnvironment = true,
185 useLocation = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000186 private static BuiltinFunction split = new BuiltinFunction("split") {
Laurent Le Brun54820742015-07-30 16:43:52 +0000187 public Object invoke(String self, String sep, Object maxSplitO, Location loc,
Florian Weikert8e2e5fc2015-05-29 12:39:08 +0000188 Environment env) throws ConversionException, EvalException {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000189 int maxSplit = Type.INTEGER.convertOptional(
190 maxSplitO, "'split' argument of 'split'", /*label*/null, -2);
191 // + 1 because the last result is the remainder, and default of -2 so that after +1 it's -1
192 String[] ss = Pattern.compile(sep, Pattern.LITERAL).split(self, maxSplit + 1);
Florian Weikert8e2e5fc2015-05-29 12:39:08 +0000193 return convert(Arrays.<String>asList(ss), env, loc);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100194 }
195 };
Laurent Le Brun54820742015-07-30 16:43:52 +0000196
Florian Weikert8e2e5fc2015-05-29 12:39:08 +0000197 @SkylarkSignature(name = "rsplit", objectType = StringModule.class,
198 returnType = HackHackEitherList.class,
199 doc = "Returns a list of all the words in the string, using <code>sep</code> "
200 + "as the separator, optionally limiting the number of splits to <code>maxsplit</code>. "
201 + "Except for splitting from the right, this method behaves like split().",
202 mandatoryPositionals = {
203 @Param(name = "self", type = String.class, doc = "This string."),
204 @Param(name = "sep", type = String.class, doc = "The string to split on.")},
205 optionalPositionals = {
206 @Param(name = "maxsplit", type = Integer.class, noneable = true,
Laurent Le Brun54820742015-07-30 16:43:52 +0000207 defaultValue = "None", doc = "The maximum number of splits.")},
Florian Weikert8e2e5fc2015-05-29 12:39:08 +0000208 useEnvironment = true,
209 useLocation = true)
210 private static BuiltinFunction rsplit = new BuiltinFunction("rsplit") {
211 @SuppressWarnings("unused")
212 public Object invoke(String self, String sep, Object maxSplitO, Location loc, Environment env)
213 throws ConversionException, EvalException {
214 int maxSplit =
215 Type.INTEGER.convertOptional(maxSplitO, "'split' argument of 'split'", null, -1);
216 List<String> result;
217
218 try {
219 result = stringRSplit(self, sep, maxSplit);
220 } catch (IllegalArgumentException ex) {
221 throw new EvalException(loc, ex);
222 }
223
224 return convert(result, env, loc);
225 }
226 };
Laurent Le Brun54820742015-07-30 16:43:52 +0000227
Florian Weikert8e2e5fc2015-05-29 12:39:08 +0000228 /**
Laurent Le Brun54820742015-07-30 16:43:52 +0000229 * Splits the given string into a list of words, using {@code separator} as a
Florian Weikert8e2e5fc2015-05-29 12:39:08 +0000230 * delimiter.
Laurent Le Brun54820742015-07-30 16:43:52 +0000231 *
Florian Weikert8e2e5fc2015-05-29 12:39:08 +0000232 * <p>At most {@code maxSplits} will be performed, going from right to left.
233 *
234 * @param input The input string.
235 * @param separator The separator string.
236 * @param maxSplits The maximum number of splits. Negative values mean unlimited splits.
237 * @return A list of words
238 * @throws IllegalArgumentException
239 */
240 private static List<String> stringRSplit(String input, String separator, int maxSplits)
241 throws IllegalArgumentException {
242 if (separator.isEmpty()) {
243 throw new IllegalArgumentException("Empty separator");
244 }
245
246 if (maxSplits <= 0) {
247 maxSplits = Integer.MAX_VALUE;
248 }
249
250 LinkedList<String> result = new LinkedList<>();
Florian Weikerte741e8f2015-06-22 20:57:01 +0000251 String[] parts = input.split(Pattern.quote(separator), -1);
Florian Weikert8e2e5fc2015-05-29 12:39:08 +0000252 int sepLen = separator.length();
Laurent Le Brun54820742015-07-30 16:43:52 +0000253 int remainingLength = input.length();
Florian Weikert8e2e5fc2015-05-29 12:39:08 +0000254 int splitsSoFar = 0;
255
Laurent Le Brun54820742015-07-30 16:43:52 +0000256 // Copies parts from the array into the final list, starting at the end (because
257 // it's rsplit), as long as fewer than maxSplits splits are performed. The
258 // last spot in the list is reserved for the remaining string, whose length
Florian Weikert8e2e5fc2015-05-29 12:39:08 +0000259 // has to be tracked throughout the loop.
260 for (int pos = parts.length - 1; (pos >= 0) && (splitsSoFar < maxSplits); --pos) {
261 String current = parts[pos];
262 result.addFirst(current);
263
264 ++splitsSoFar;
265 remainingLength -= sepLen + current.length();
266 }
267
268 if (splitsSoFar == maxSplits && remainingLength >= 0) {
Laurent Le Brun54820742015-07-30 16:43:52 +0000269 result.addFirst(input.substring(0, remainingLength));
Florian Weikert8e2e5fc2015-05-29 12:39:08 +0000270 }
Laurent Le Brun54820742015-07-30 16:43:52 +0000271
272 return result;
Florian Weikert8e2e5fc2015-05-29 12:39:08 +0000273 }
Laurent Le Brun54820742015-07-30 16:43:52 +0000274
Googler7ad2f092015-05-28 11:04:47 +0000275 @SkylarkSignature(name = "partition", objectType = StringModule.class,
276 returnType = HackHackEitherList.class,
277 doc = "Splits the input string at the first occurrence of the separator "
278 + "<code>sep</code> and returns the resulting partition as a three-element "
279 + "list of the form [substring_before, separator, substring_after].",
280 mandatoryPositionals = {
281 @Param(name = "self", type = String.class, doc = "This string.")},
282 optionalPositionals = {
283 @Param(name = "sep", type = String.class,
284 defaultValue = "' '", doc = "The string to split on, default is space (\" \").")},
285 useEnvironment = true,
286 useLocation = true)
287 private static BuiltinFunction partition = new BuiltinFunction("partition") {
288 @SuppressWarnings("unused")
289 public Object invoke(String self, String sep, Location loc, Environment env)
290 throws EvalException {
Florian Weikert8e2e5fc2015-05-29 12:39:08 +0000291 return partitionWrapper(self, sep, true, env, loc);
Googler7ad2f092015-05-28 11:04:47 +0000292 }
293 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100294
Googler7ad2f092015-05-28 11:04:47 +0000295 @SkylarkSignature(name = "rpartition", objectType = StringModule.class,
296 returnType = HackHackEitherList.class,
297 doc = "Splits the input string at the last occurrence of the separator "
298 + "<code>sep</code> and returns the resulting partition as a three-element "
299 + "list of the form [substring_before, separator, substring_after].",
300 mandatoryPositionals = {
301 @Param(name = "self", type = String.class, doc = "This string.")},
302 optionalPositionals = {
303 @Param(name = "sep", type = String.class,
304 defaultValue = "' '", doc = "The string to split on, default is space (\" \").")},
305 useEnvironment = true,
306 useLocation = true)
307 private static BuiltinFunction rpartition = new BuiltinFunction("rpartition") {
308 @SuppressWarnings("unused")
309 public Object invoke(String self, String sep, Location loc, Environment env)
310 throws EvalException {
Florian Weikert8e2e5fc2015-05-29 12:39:08 +0000311 return partitionWrapper(self, sep, false, env, loc);
Googler7ad2f092015-05-28 11:04:47 +0000312 }
313 };
314
315 /**
Laurent Le Brun54820742015-07-30 16:43:52 +0000316 * Wraps the stringPartition() method and converts its results and exceptions
Googler7ad2f092015-05-28 11:04:47 +0000317 * to the expected types.
318 *
319 * @param self The input string
320 * @param separator The string to split on
Laurent Le Brun54820742015-07-30 16:43:52 +0000321 * @param forward A flag that controls whether the input string is split around
322 * the first ({@code true}) or last ({@code false}) occurrence of the separator.
Florian Weikert8e2e5fc2015-05-29 12:39:08 +0000323 * @param env The current environment
Googler7ad2f092015-05-28 11:04:47 +0000324 * @param loc The location that is used for potential exceptions
325 * @return A list with three elements
326 */
327 private static Object partitionWrapper(String self, String separator, boolean forward,
Florian Weikert8e2e5fc2015-05-29 12:39:08 +0000328 Environment env, Location loc) throws EvalException {
Googler7ad2f092015-05-28 11:04:47 +0000329 try {
Florian Weikert8e2e5fc2015-05-29 12:39:08 +0000330 return convert(stringPartition(self, separator, forward), env, loc);
Googler7ad2f092015-05-28 11:04:47 +0000331 } catch (IllegalArgumentException ex) {
332 throw new EvalException(loc, ex);
333 }
334 }
335
336 /**
337 * Splits the input string at the {first|last} occurrence of the given separator
Laurent Le Brun54820742015-07-30 16:43:52 +0000338 * and returns the resulting partition as a three-tuple of Strings, contained
Googler7ad2f092015-05-28 11:04:47 +0000339 * in a {@code List}.
Laurent Le Brun54820742015-07-30 16:43:52 +0000340 *
Googler7ad2f092015-05-28 11:04:47 +0000341 * <p>If the input string does not contain the separator, the tuple will
342 * consist of the original input string and two empty strings.
Laurent Le Brun54820742015-07-30 16:43:52 +0000343 *
344 * <p>This method emulates the behavior of Python's str.partition() and
Googler7ad2f092015-05-28 11:04:47 +0000345 * str.rpartition(), depending on the value of the {@code forward} flag.
Laurent Le Brun54820742015-07-30 16:43:52 +0000346 *
Googler7ad2f092015-05-28 11:04:47 +0000347 * @param input The input string
348 * @param separator The string to split on
Laurent Le Brun54820742015-07-30 16:43:52 +0000349 * @param forward A flag that controls whether the input string is split around
350 * the first ({@code true}) or last ({@code false}) occurrence of the separator.
351 * @return A three-tuple (List) of the form [part_before_separator, separator,
Googler7ad2f092015-05-28 11:04:47 +0000352 * part_after_separator].
Laurent Le Brun54820742015-07-30 16:43:52 +0000353 *
Googler7ad2f092015-05-28 11:04:47 +0000354 */
355 private static List<String> stringPartition(String input, String separator, boolean forward)
356 throws IllegalArgumentException {
357 if (separator.isEmpty()) {
358 throw new IllegalArgumentException("Empty separator");
359 }
360
361 int partitionSize = 3;
362 ArrayList<String> result = new ArrayList<>(partitionSize);
363 int pos = forward ? input.indexOf(separator) : input.lastIndexOf(separator);
364
365 if (pos < 0) {
366 for (int i = 0; i < partitionSize; ++i) {
367 result.add("");
368 }
369
370 // Following Python's implementation of str.partition() and str.rpartition(),
371 // the input string is copied to either the first or the last position in the
372 // list, depending on the value of the forward flag.
373 result.set(forward ? 0 : partitionSize - 1, input);
374 } else {
375 result.add(input.substring(0, pos));
376 result.add(separator);
377
378 // pos + sep.length() is at most equal to input.length(). This worst-case
379 // happens when the separator is at the end of the input string. However,
380 // substring() will return an empty string in this scenario, thus making
381 // any additional safety checks obsolete.
382 result.add(input.substring(pos + separator.length()));
383 }
384
385 return result;
386 }
Laurent Le Brun54820742015-07-30 16:43:52 +0000387
Laurent Le Brunb4114cc2015-08-26 14:53:37 +0000388 @SkylarkSignature(
389 name = "capitalize",
390 objectType = StringModule.class,
391 returnType = String.class,
392 doc =
393 "Returns a copy of the string with its first character capitalized and the rest"
394 + "lowercased. This method does not support non-ascii characters.",
395 mandatoryPositionals = {@Param(name = "self", type = String.class, doc = "This string.")}
396 )
397 private static BuiltinFunction capitalize =
398 new BuiltinFunction("capitalize") {
399 @SuppressWarnings("unused")
400 public String invoke(String self) throws EvalException {
401 if (self.isEmpty()) {
402 return self;
403 }
404 return Character.toUpperCase(self.charAt(0)) + self.substring(1).toLowerCase();
405 }
406 };
407
Florian Weikert006bf4f2015-08-03 12:28:35 +0000408 @SkylarkSignature(name = "title", objectType = StringModule.class,
409 returnType = String.class,
410 doc =
411 "Converts the input string into title case, i.e. every word starts with an "
412 + "uppercase letter while the remaining letters are lowercase. In this "
413 + "context, a word means strictly a sequence of letters. This method does "
414 + "not support supplementary Unicode characters.",
415 mandatoryPositionals = {
416 @Param(name = "self", type = String.class, doc = "This string.")})
417 private static BuiltinFunction title = new BuiltinFunction("title") {
418 @SuppressWarnings("unused")
419 public String invoke(String self) throws EvalException {
420 char[] data = self.toCharArray();
421 boolean previousWasLetter = false;
422
423 for (int pos = 0; pos < data.length; ++pos) {
424 char current = data[pos];
425 boolean currentIsLetter = Character.isLetter(current);
426
427 if (currentIsLetter) {
428 if (previousWasLetter && Character.isUpperCase(current)) {
429 data[pos] = Character.toLowerCase(current);
430 } else if (!previousWasLetter && Character.isLowerCase(current)) {
431 data[pos] = Character.toUpperCase(current);
432 }
433 }
434 previousWasLetter = currentIsLetter;
435 }
436
437 return new String(data);
438 }
439 };
440
Francois-Rene Rideau76023b92015-04-17 15:31:59 +0000441 /**
442 * Common implementation for find, rfind, index, rindex.
443 * @param forward true if we want to return the last matching index.
444 */
445 private static int stringFind(boolean forward,
446 String self, String sub, int start, Object end, String msg)
447 throws ConversionException {
448 String substr = pythonSubstring(self, start, end, msg);
449 int subpos = forward ? substr.indexOf(sub) : substr.lastIndexOf(sub);
450 start = clampIndex(start, self.length());
451 return subpos < 0 ? subpos : subpos + start;
452 }
453
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000454 @SkylarkSignature(name = "rfind", objectType = StringModule.class, returnType = Integer.class,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100455 doc = "Returns the last index where <code>sub</code> is found, "
456 + "or -1 if no such index exists, optionally restricting to "
457 + "[<code>start</code>:<code>end</code>], "
458 + "<code>start</code> being inclusive and <code>end</code> being exclusive.",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000459 mandatoryPositionals = {
460 @Param(name = "self", type = String.class, doc = "This string."),
461 @Param(name = "sub", type = String.class, doc = "The substring to find.")},
462 optionalPositionals = {
463 @Param(name = "start", type = Integer.class, defaultValue = "0",
464 doc = "Restrict to search from this position."),
465 @Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None",
466 doc = "optional position before which to restrict to search.")})
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000467 private static BuiltinFunction rfind = new BuiltinFunction("rfind") {
468 public Integer invoke(String self, String sub, Integer start, Object end)
469 throws ConversionException {
470 return stringFind(false, self, sub, start, end, "'end' argument to rfind");
471 }
472 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100473
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000474 @SkylarkSignature(name = "find", objectType = StringModule.class, returnType = Integer.class,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100475 doc = "Returns the first index where <code>sub</code> is found, "
476 + "or -1 if no such index exists, optionally restricting to "
477 + "[<code>start</code>:<code>end]</code>, "
478 + "<code>start</code> being inclusive and <code>end</code> being exclusive.",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000479 mandatoryPositionals = {
480 @Param(name = "self", type = String.class, doc = "This string."),
481 @Param(name = "sub", type = String.class, doc = "The substring to find.")},
482 optionalPositionals = {
483 @Param(name = "start", type = Integer.class, defaultValue = "0",
484 doc = "Restrict to search from this position."),
485 @Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None",
486 doc = "optional position before which to restrict to search.")})
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000487 private static BuiltinFunction find = new BuiltinFunction("find") {
488 public Integer invoke(String self, String sub, Integer start, Object end)
489 throws ConversionException {
490 return stringFind(true, self, sub, start, end, "'end' argument to find");
491 }
492 };
Laurent Le Brun4e116c72015-03-23 13:48:50 +0000493
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000494 @SkylarkSignature(name = "rindex", objectType = StringModule.class, returnType = Integer.class,
Laurent Le Brun4e116c72015-03-23 13:48:50 +0000495 doc = "Returns the last index where <code>sub</code> is found, "
Laurent Le Brun18d84992015-08-19 12:13:36 +0000496 + "or raises an error if no such index exists, optionally restricting to "
Laurent Le Brun4e116c72015-03-23 13:48:50 +0000497 + "[<code>start</code>:<code>end</code>], "
498 + "<code>start</code> being inclusive and <code>end</code> being exclusive.",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000499 mandatoryPositionals = {
500 @Param(name = "self", type = String.class, doc = "This string."),
501 @Param(name = "sub", type = String.class, doc = "The substring to find.")},
502 optionalPositionals = {
503 @Param(name = "start", type = Integer.class, defaultValue = "0",
504 doc = "Restrict to search from this position."),
505 @Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None",
506 doc = "optional position before which to restrict to search.")},
507 useLocation = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000508 private static BuiltinFunction rindex = new BuiltinFunction("rindex") {
509 public Integer invoke(String self, String sub, Integer start, Object end,
510 Location loc) throws EvalException, ConversionException {
511 int res = stringFind(false, self, sub, start, end, "'end' argument to rindex");
512 if (res < 0) {
Francois-Rene Rideau8d5cce32015-06-16 23:12:04 +0000513 throw new EvalException(loc, Printer.format("substring %r not found in %r", sub, self));
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000514 }
515 return res;
516 }
517 };
Laurent Le Brun4e116c72015-03-23 13:48:50 +0000518
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000519 @SkylarkSignature(name = "index", objectType = StringModule.class, returnType = Integer.class,
Laurent Le Brun4e116c72015-03-23 13:48:50 +0000520 doc = "Returns the first index where <code>sub</code> is found, "
Laurent Le Brun18d84992015-08-19 12:13:36 +0000521 + "or raises an error if no such index exists, optionally restricting to "
Laurent Le Brun4e116c72015-03-23 13:48:50 +0000522 + "[<code>start</code>:<code>end]</code>, "
523 + "<code>start</code> being inclusive and <code>end</code> being exclusive.",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000524 mandatoryPositionals = {
525 @Param(name = "self", type = String.class, doc = "This string."),
526 @Param(name = "sub", type = String.class, doc = "The substring to find.")},
527 optionalPositionals = {
528 @Param(name = "start", type = Integer.class, defaultValue = "0",
529 doc = "Restrict to search from this position."),
530 @Param(name = "end", type = Integer.class, noneable = true,
531 doc = "optional position before which to restrict to search.")},
532 useLocation = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000533 private static BuiltinFunction index = new BuiltinFunction("index") {
534 public Integer invoke(String self, String sub, Integer start, Object end,
535 Location loc) throws EvalException, ConversionException {
536 int res = stringFind(true, self, sub, start, end, "'end' argument to index");
537 if (res < 0) {
Francois-Rene Rideau8d5cce32015-06-16 23:12:04 +0000538 throw new EvalException(loc, Printer.format("substring %r not found in %r", sub, self));
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000539 }
540 return res;
541 }
542 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100543
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000544 @SkylarkSignature(name = "count", objectType = StringModule.class, returnType = Integer.class,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100545 doc = "Returns the number of (non-overlapping) occurrences of substring <code>sub</code> in "
546 + "string, optionally restricting to [<code>start</code>:<code>end</code>], "
547 + "<code>start</code> being inclusive and <code>end</code> being exclusive.",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000548 mandatoryPositionals = {
549 @Param(name = "self", type = String.class, doc = "This string."),
550 @Param(name = "sub", type = String.class, doc = "The substring to count.")},
551 optionalPositionals = {
552 @Param(name = "start", type = Integer.class, defaultValue = "0",
553 doc = "Restrict to search from this position."),
554 @Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None",
555 doc = "optional position before which to restrict to search.")})
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000556 private static BuiltinFunction count = new BuiltinFunction("count") {
557 public Integer invoke(String self, String sub, Integer start, Object end)
558 throws ConversionException {
559 String str = pythonSubstring(self, start, end, "'end' operand of 'find'");
560 if (sub.isEmpty()) {
561 return str.length() + 1;
562 }
563 int count = 0;
564 int index = -1;
565 while ((index = str.indexOf(sub)) >= 0) {
566 count++;
567 str = str.substring(index + sub.length());
568 }
569 return count;
570 }
571 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100572
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000573 @SkylarkSignature(name = "endswith", objectType = StringModule.class, returnType = Boolean.class,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100574 doc = "Returns True if the string ends with <code>sub</code>, "
575 + "otherwise False, optionally restricting to [<code>start</code>:<code>end</code>], "
576 + "<code>start</code> being inclusive and <code>end</code> being exclusive.",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000577 mandatoryPositionals = {
578 @Param(name = "self", type = String.class, doc = "This string."),
579 @Param(name = "sub", type = String.class, doc = "The substring to check.")},
580 optionalPositionals = {
581 @Param(name = "start", type = Integer.class, defaultValue = "0",
582 doc = "Test beginning at this position."),
583 @Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None",
584 doc = "optional position at which to stop comparing.")})
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000585 private static BuiltinFunction endswith = new BuiltinFunction(
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000586 "endswith", SkylarkList.tuple(0, Runtime.NONE)) {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000587 public Boolean invoke(String self, String sub, Integer start, Object end)
588 throws ConversionException {
589 return pythonSubstring(self, start, end, "'end' operand of 'endswith'").endsWith(sub);
590 }
591 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100592
Laurent Le Brunaa83c3a2015-05-08 15:00:35 +0000593 // In Python, formatting is very complex.
594 // We handle here the simplest case which provides most of the value of the function.
595 // https://docs.python.org/3/library/string.html#formatstrings
596 @SkylarkSignature(name = "format", objectType = StringModule.class, returnType = String.class,
Laurent Le Brun18d84992015-08-19 12:13:36 +0000597 doc = "Perform string interpolation. Format strings contain replacement fields "
598 + "surrounded by curly braces <code>{}</code>. Anything that is not contained "
599 + "in braces is considered literal text, which is copied unchanged to the output."
600 + "If you need to include a brace character in the literal text, it can be "
601 + "escaped by doubling: <code>{{</code> and <code>}}</code>"
602 + "A replacement field can be either a name, a number, or empty. Values are "
603 + "converted to strings using the <a href=\"globals.html#str\">str</a> function."
Laurent Le Brunaa83c3a2015-05-08 15:00:35 +0000604 + "<pre class=\"language-python\">"
Laurent Le Brun18d84992015-08-19 12:13:36 +0000605 + "# Access in order:\n"
606 + "\"{} < {}\".format(4, 5) == \"4 < 5\"\n"
607 + "# Access by position:\n"
608 + "\"{1}, {0}\".format(2, 1) == \"1, 2\"\n"
609 + "# Access by name:\n"
Laurent Le Brunaa83c3a2015-05-08 15:00:35 +0000610 + "\"x{key}x\".format(key = 2) == \"x2x\"</pre>\n",
611 mandatoryPositionals = {
Florian Weikert8a84dd92015-06-26 15:39:57 +0000612 @Param(name = "self", type = String.class, doc = "This string."),
Laurent Le Brunaa83c3a2015-05-08 15:00:35 +0000613 },
Florian Weikert8a84dd92015-06-26 15:39:57 +0000614 extraPositionals = {
615 @Param(name = "args", type = HackHackEitherList.class, defaultValue = "[]",
616 doc = "List of arguments"),
617 },
618 extraKeywords = {@Param(name = "kwargs", doc = "Dictionary of arguments")},
Laurent Le Brunaa83c3a2015-05-08 15:00:35 +0000619 useLocation = true)
620 private static BuiltinFunction format = new BuiltinFunction("format") {
Florian Weikert8a84dd92015-06-26 15:39:57 +0000621 @SuppressWarnings("unused")
622 public String invoke(String self, Object args, Map<String, Object> kwargs, Location loc)
623 throws ConversionException, EvalException {
624 return new FormatParser(loc).format(self, Type.OBJECT_LIST.convert(args, "format"), kwargs);
Laurent Le Brunaa83c3a2015-05-08 15:00:35 +0000625 }
626 };
627
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000628 @SkylarkSignature(name = "startswith", objectType = StringModule.class,
629 returnType = Boolean.class,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100630 doc = "Returns True if the string starts with <code>sub</code>, "
631 + "otherwise False, optionally restricting to [<code>start</code>:<code>end</code>], "
632 + "<code>start</code> being inclusive and <code>end</code> being exclusive.",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000633 mandatoryPositionals = {
634 @Param(name = "self", type = String.class, doc = "This string."),
635 @Param(name = "sub", type = String.class, doc = "The substring to check.")},
636 optionalPositionals = {
637 @Param(name = "start", type = Integer.class, defaultValue = "0",
638 doc = "Test beginning at this position."),
639 @Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None",
640 doc = "Stop comparing at this position.")})
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000641 private static BuiltinFunction startswith = new BuiltinFunction("startswith") {
642 public Boolean invoke(String self, String sub, Integer start, Object end)
643 throws ConversionException {
644 return pythonSubstring(self, start, end, "'end' operand of 'startswith'").startsWith(sub);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100645 }
646 };
647
648 // TODO(bazel-team): Maybe support an argument to tell the type of the whitespace.
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000649 @SkylarkSignature(name = "strip", objectType = StringModule.class, returnType = String.class,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100650 doc = "Returns a copy of the string in which all whitespace characters "
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000651 + "have been stripped from the beginning and the end of the string.",
652 mandatoryPositionals = {
653 @Param(name = "self", type = String.class, doc = "This string.")})
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000654 private static BuiltinFunction strip = new BuiltinFunction("strip") {
655 public String invoke(String self) {
656 return self.trim();
657 }
658 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100659
Laurent Le Bruneeef30f2015-03-16 15:12:35 +0000660 // slice operator
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000661 @SkylarkSignature(name = "$slice", objectType = String.class,
662 documented = false,
663 mandatoryPositionals = {
664 @Param(name = "self", type = String.class, doc = "This string."),
665 @Param(name = "start", type = Integer.class, doc = "start position of the slice."),
666 @Param(name = "end", type = Integer.class, doc = "end position of the slice.")},
667 doc = "x[<code>start</code>:<code>end</code>] returns a slice or a list slice.")
668 private static BuiltinFunction stringSlice = new BuiltinFunction("$slice") {
669 public Object invoke(String self, Integer left, Integer right)
670 throws EvalException, ConversionException {
671 return pythonSubstring(self, left, right, "");
672 }
673 };
Laurent Le Bruneeef30f2015-03-16 15:12:35 +0000674
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000675 @SkylarkSignature(name = "$slice", objectType = List.class,
676 documented = false,
677 mandatoryPositionals = {
678 @Param(name = "self", type = List.class, doc = "This list or tuple."),
679 @Param(name = "start", type = Integer.class, doc = "start position of the slice."),
680 @Param(name = "end", type = Integer.class, doc = "end position of the slice.")},
681 doc = "x[<code>start</code>:<code>end</code>] returns a slice or a list slice.")
682 private static BuiltinFunction listSlice = new BuiltinFunction("$slice") {
683 public Object invoke(List<Object> self, Integer left, Integer right)
684 throws EvalException, ConversionException {
685 return sliceList(self, left, right);
686 }
687 };
688
689 @SkylarkSignature(name = "$slice", objectType = SkylarkList.class,
690 documented = false,
691 mandatoryPositionals = {
692 @Param(name = "self", type = SkylarkList.class, doc = "This list or tuple."),
693 @Param(name = "start", type = Integer.class, doc = "start position of the slice."),
694 @Param(name = "end", type = Integer.class, doc = "end position of the slice.")},
695 doc = "x[<code>start</code>:<code>end</code>] returns a slice or a list slice.",
696 useLocation = true)
697 private static BuiltinFunction skylarkListSlice = new BuiltinFunction("$slice") {
698 public Object invoke(SkylarkList self, Integer left, Integer right,
699 Location loc) throws EvalException, ConversionException {
700 return SkylarkList.list(sliceList(self.toList(), left, right), loc);
701 }
702 };
703
704 private static List<Object> sliceList(List<Object> list, Integer left, Integer right) {
705 left = clampIndex(left, list.size());
706 right = clampIndex(right, list.size());
707 if (left > right) {
708 left = right;
709 }
710 return list.subList(left, right);
711 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100712
713 // supported list methods
Laurent Le Brun6a4d36a2015-08-21 10:57:41 +0000714 @SkylarkSignature(
715 name = "sorted",
716 returnType = HackHackEitherList.class,
717 doc =
718 "Sort a collection. Elements are sorted first by their type, "
719 + "then by their value (in ascending order).",
720 mandatoryPositionals = {@Param(name = "self", type = Object.class, doc = "This collection.")},
721 useLocation = true,
722 useEnvironment = true
723 )
724 private static BuiltinFunction sorted =
725 new BuiltinFunction("sorted") {
726 public Object invoke(Object self, Location loc, Environment env)
727 throws EvalException, ConversionException {
728 try {
729 Collection<?> col =
730 Ordering.from(EvalUtils.SKYLARK_COMPARATOR)
731 .sortedCopy(EvalUtils.toCollection(self, loc));
732 return convert(col, env, loc);
733 } catch (EvalUtils.ComparisonException e) {
734 throw new EvalException(loc, e);
735 }
736 }
737 };
Laurent Le Brunef69ec52015-04-16 18:58:34 +0000738
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000739 // This function has a SkylarkSignature but is only used by the Build language, not Skylark.
Laurent Le Brun6a4d36a2015-08-21 10:57:41 +0000740 @SkylarkSignature(
741 name = "append",
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000742 objectType = List.class,
743 returnType = Runtime.NoneType.class,
Laurent Le Brun6a4d36a2015-08-21 10:57:41 +0000744 documented = false,
745 doc = "Adds an item to the end of the list.",
746 mandatoryPositionals = {
747 // we use List rather than SkylarkList because this is actually for use *outside* Skylark
748 @Param(name = "self", type = List.class, doc = "This list."),
749 @Param(name = "item", type = Object.class, doc = "Item to add at the end.")
750 },
751 useLocation = true,
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000752 useEnvironment = true)
Laurent Le Brun6a4d36a2015-08-21 10:57:41 +0000753 private static BuiltinFunction append =
754 new BuiltinFunction("append") {
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000755 public Runtime.NoneType invoke(List<Object> self, Object item,
756 Location loc, Environment env) throws EvalException, ConversionException {
757 if (env.isSkylark()) {
758 throw new EvalException(loc,
759 "function 'append' is not available in Skylark");
760 }
761 if (EvalUtils.isTuple(self)) {
762 throw new EvalException(loc,
763 "function 'append' is not defined on object of type 'Tuple'");
764 }
Laurent Le Brun6a4d36a2015-08-21 10:57:41 +0000765 self.add(item);
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000766 return Runtime.NONE;
Laurent Le Brun6a4d36a2015-08-21 10:57:41 +0000767 }
768 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100769
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000770 // This function has a SkylarkSignature but is only used by the Build language, not Skylark.
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000771 @SkylarkSignature(
772 name = "extend",
773 objectType = List.class,
774 returnType = Runtime.NoneType.class,
775 documented = false,
776 doc = "Adds all items to the end of the list.",
777 mandatoryPositionals = {
778 // we use List rather than SkylarkList because this is actually for use *outside* Skylark
779 @Param(name = "self", type = List.class, doc = "This list."),
780 @Param(name = "items", type = List.class, doc = "Items to add at the end.")},
781 useLocation = true,
782 useEnvironment = true)
783 private static BuiltinFunction extend =
784 new BuiltinFunction("extend") {
785 public Runtime.NoneType invoke(List<Object> self, List<Object> items,
786 Location loc, Environment env) throws EvalException, ConversionException {
787 if (env.isSkylark()) {
788 throw new EvalException(loc,
789 "function 'append' is not available in Skylark");
790 }
791 if (EvalUtils.isTuple(self)) {
792 throw new EvalException(loc,
793 "function 'extend' is not defined on object of type 'Tuple'");
794 }
795 self.addAll(items);
796 return Runtime.NONE;
797 }
798 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100799
800 // dictionary access operator
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000801 @SkylarkSignature(name = "$index", documented = false, objectType = Map.class,
802 doc = "Looks up a value in a dictionary.",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000803 mandatoryPositionals = {
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000804 @Param(name = "self", type = Map.class, doc = "This object."),
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000805 @Param(name = "key", type = Object.class, doc = "The index or key to access.")},
806 useLocation = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000807 private static BuiltinFunction indexOperator = new BuiltinFunction("$index") {
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000808 public Object invoke(Map<?, ?> self, Object key,
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000809 Location loc) throws EvalException, ConversionException {
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000810 if (!self.containsKey(key)) {
811 throw new EvalException(loc, Printer.format("Key %r not found in dictionary", key));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100812 }
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000813 return self.get(key);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100814 }
815 };
816
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000817 // list access operator
818 @SkylarkSignature(name = "$index", documented = false, objectType = SkylarkList.class,
819 doc = "Returns the nth element of a list.",
820 mandatoryPositionals = {
821 @Param(name = "self", type = SkylarkList.class, doc = "This object."),
822 @Param(name = "key", type = Object.class, doc = "The index or key to access.")},
823 useLocation = true)
824 private static BuiltinFunction skylarkListIndexOperator = new BuiltinFunction("$index") {
825 public Object invoke(SkylarkList self, Object key,
826 Location loc) throws EvalException, ConversionException {
827 if (self.isEmpty()) {
828 throw new EvalException(loc, "List is empty");
829 }
830 int index = getListIndex(key, self.size(), loc);
831 return self.get(index);
832 }
833 };
834
835
836 // list access operator
837 @SkylarkSignature(name = "$index", documented = false, objectType = List.class,
838 doc = "Returns the nth element of a list.",
839 mandatoryPositionals = {
840 @Param(name = "self", type = List.class, doc = "This object."),
841 @Param(name = "key", type = Object.class, doc = "The index or key to access.")},
842 useLocation = true)
843 private static BuiltinFunction listIndexOperator = new BuiltinFunction("$index") {
844 public Object invoke(List<?> self, Object key,
845 Location loc) throws EvalException, ConversionException {
846 if (self.isEmpty()) {
847 throw new EvalException(loc, "List is empty");
848 }
849 int index = getListIndex(key, self.size(), loc);
850 return self.get(index);
851 }
852 };
853
854 @SkylarkSignature(name = "$index", documented = false, objectType = String.class,
855 doc = "Returns the nth element of a string.",
856 mandatoryPositionals = {
857 @Param(name = "self", type = String.class, doc = "This string."),
858 @Param(name = "key", type = Object.class, doc = "The index or key to access.")},
859 useLocation = true)
860 private static BuiltinFunction stringIndexOperator = new BuiltinFunction("$index") {
861 public Object invoke(String self, Object key,
862 Location loc) throws EvalException, ConversionException {
863 int index = getListIndex(key, self.length(), loc);
864 return self.substring(index, index + 1);
865 }
866 };
867
868 @SkylarkSignature(name = "values", objectType = Map.class,
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000869 returnType = HackHackEitherList.class,
Laurent Le Brun9d27a012015-03-31 12:28:02 +0000870 doc = "Return the list of values. Dictionaries are always sorted by their keys:"
871 + "<pre class=\"language-python\">"
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000872 + "{2: \"a\", 4: \"b\", 1: \"c\"}.values() == [\"c\", \"a\", \"b\"]</pre>\n",
873 mandatoryPositionals = {@Param(name = "self", type = Map.class, doc = "This dict.")},
874 useLocation = true, useEnvironment = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000875 private static BuiltinFunction values = new BuiltinFunction("values") {
876 public Object invoke(Map<?, ?> self,
877 Location loc, Environment env) throws EvalException, ConversionException {
Laurent Le Brun196c1a72015-03-18 13:03:04 +0000878 // Use a TreeMap to ensure consistent ordering.
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000879 Map<?, ?> dict = new TreeMap<>(self);
880 return convert(dict.values(), env, loc);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100881 }
882 };
883
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000884 @SkylarkSignature(name = "items", objectType = Map.class,
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000885 returnType = HackHackEitherList.class,
Laurent Le Brun9d27a012015-03-31 12:28:02 +0000886 doc = "Return the list of key-value tuples. Dictionaries are always sorted by their keys:"
887 + "<pre class=\"language-python\">"
888 + "{2: \"a\", 4: \"b\", 1: \"c\"}.items() == [(1, \"c\"), (2, \"a\"), (4, \"b\")]"
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000889 + "</pre>\n",
890 mandatoryPositionals = {
891 @Param(name = "self", type = Map.class, doc = "This dict.")},
892 useLocation = true, useEnvironment = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000893 private static BuiltinFunction items = new BuiltinFunction("items") {
894 public Object invoke(Map<?, ?> self,
895 Location loc, Environment env) throws EvalException, ConversionException {
Laurent Le Brun196c1a72015-03-18 13:03:04 +0000896 // Use a TreeMap to ensure consistent ordering.
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000897 Map<?, ?> dict = new TreeMap<>(self);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100898 List<Object> list = Lists.newArrayListWithCapacity(dict.size());
899 for (Map.Entry<?, ?> entries : dict.entrySet()) {
900 List<?> item = ImmutableList.of(entries.getKey(), entries.getValue());
Francois-Rene Rideau22aab6d2015-08-25 22:55:31 +0000901 list.add(env.isSkylark() ? SkylarkList.tuple(item) : item);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100902 }
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000903 return convert(list, env, loc);
904 }
905 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100906
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000907 @SkylarkSignature(name = "keys", objectType = Map.class,
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000908 returnType = HackHackEitherList.class,
Laurent Le Brun9d27a012015-03-31 12:28:02 +0000909 doc = "Return the list of keys. Dictionaries are always sorted by their keys:"
910 + "<pre class=\"language-python\">{2: \"a\", 4: \"b\", 1: \"c\"}.keys() == [1, 2, 4]"
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000911 + "</pre>\n",
912 mandatoryPositionals = {
913 @Param(name = "self", type = Map.class, doc = "This dict.")},
914 useLocation = true, useEnvironment = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000915 // Skylark will only call this on a dict; and
916 // allowed keys are all Comparable... if not mutually, it's OK to get a runtime exception.
917 private static BuiltinFunction keys = new BuiltinFunction("keys") {
918 public Object invoke(Map<Comparable<?>, ?> dict,
919 Location loc, Environment env) throws EvalException {
920 return convert(Ordering.natural().sortedCopy(dict.keySet()), env, loc);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100921 }
922 };
923
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000924 @SkylarkSignature(name = "get", objectType = Map.class,
Laurent Le Brunf9148972015-03-31 13:54:36 +0000925 doc = "Return the value for <code>key</code> if <code>key</code> is in the dictionary, "
926 + "else <code>default</code>. If <code>default</code> is not given, it defaults to "
927 + "<code>None</code>, so that this method never throws an error.",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000928 mandatoryPositionals = {
929 @Param(name = "self", doc = "This dict."),
Laurent Le Brunf9148972015-03-31 13:54:36 +0000930 @Param(name = "key", doc = "The key to look for.")},
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000931 optionalPositionals = {
932 @Param(name = "default", defaultValue = "None",
Laurent Le Brunf9148972015-03-31 13:54:36 +0000933 doc = "The default value to use (instead of None) if the key is not found.")})
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000934 private static BuiltinFunction get = new BuiltinFunction("get") {
935 public Object invoke(Map<?, ?> self, Object key, Object defaultValue) {
936 if (self.containsKey(key)) {
937 return self.get(key);
Laurent Le Brunf9148972015-03-31 13:54:36 +0000938 }
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000939 return defaultValue;
Laurent Le Brunf9148972015-03-31 13:54:36 +0000940 }
941 };
942
Laurent Le Bruneeef30f2015-03-16 15:12:35 +0000943 // TODO(bazel-team): Use the same type for both Skylark and BUILD files.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100944 @SuppressWarnings("unchecked")
945 private static Iterable<Object> convert(Collection<?> list, Environment env, Location loc)
946 throws EvalException {
Francois-Rene Rideau22aab6d2015-08-25 22:55:31 +0000947 if (env.isSkylark()) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100948 return SkylarkList.list(list, loc);
949 } else {
950 return Lists.newArrayList(list);
951 }
952 }
953
954 // unary minus
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000955 @SkylarkSignature(name = "-", returnType = Integer.class,
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000956 documented = false,
957 doc = "Unary minus operator.",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000958 mandatoryPositionals = {
959 @Param(name = "num", type = Integer.class, doc = "The number to negate.")})
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000960 private static BuiltinFunction minus = new BuiltinFunction("-") {
961 public Integer invoke(Integer num) throws ConversionException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100962 return -num;
963 }
964 };
965
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000966 @SkylarkSignature(name = "list", returnType = SkylarkList.class,
Laurent Le Brun9d27a012015-03-31 12:28:02 +0000967 doc = "Converts a collection (e.g. set or dictionary) to a list."
968 + "<pre class=\"language-python\">list([1, 2]) == [1, 2]\n"
969 + "list(set([2, 3, 2])) == [2, 3]\n"
970 + "list({5: \"a\", 2: \"b\", 4: \"c\"}) == [2, 4, 5]</pre>",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000971 mandatoryPositionals = {@Param(name = "x", doc = "The object to convert.")},
972 useLocation = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000973 private static BuiltinFunction list = new BuiltinFunction("list") {
974 public SkylarkList invoke(Object x, Location loc) throws EvalException {
975 return SkylarkList.list(EvalUtils.toCollection(x, loc), loc);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100976 }
977 };
978
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000979 @SkylarkSignature(name = "len", returnType = Integer.class, doc =
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100980 "Returns the length of a string, list, tuple, set, or dictionary.",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000981 mandatoryPositionals = {@Param(name = "x", doc = "The object to check length of.")},
982 useLocation = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000983 private static BuiltinFunction len = new BuiltinFunction("len") {
984 public Integer invoke(Object x, Location loc) throws EvalException {
985 int l = EvalUtils.size(x);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100986 if (l == -1) {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000987 throw new EvalException(loc, EvalUtils.getDataTypeName(x) + " is not iterable");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100988 }
989 return l;
990 }
991 };
992
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000993 @SkylarkSignature(name = "str", returnType = String.class, doc =
Laurent Le Brunfaf78412015-07-28 16:13:00 +0000994 "Converts any object to string. This is useful for debugging."
995 + "<pre class=\"language-python\">str(\"ab\") == \"ab\"</pre>",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000996 mandatoryPositionals = {@Param(name = "x", doc = "The object to convert.")})
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000997 private static BuiltinFunction str = new BuiltinFunction("str") {
Francois-Rene Rideaud61f5312015-06-13 03:34:47 +0000998 public String invoke(Object x) {
999 return Printer.str(x);
1000 }
1001 };
1002
1003 @SkylarkSignature(name = "repr", returnType = String.class, doc =
Laurent Le Brunfaf78412015-07-28 16:13:00 +00001004 "Converts any object to a string representation. This is useful for debugging.<br>"
1005 + "<pre class=\"language-python\">str(\"ab\") == \\\"ab\\\"</pre>",
Francois-Rene Rideaud61f5312015-06-13 03:34:47 +00001006 mandatoryPositionals = {@Param(name = "x", doc = "The object to convert.")})
1007 private static BuiltinFunction repr = new BuiltinFunction("repr") {
1008 public String invoke(Object x) {
1009 return Printer.repr(x);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001010 }
1011 };
1012
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001013 @SkylarkSignature(name = "bool", returnType = Boolean.class,
Laurent Le Brunfaf78412015-07-28 16:13:00 +00001014 doc = "Constructor for the bool type. "
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001015 + "It returns False if the object is None, False, an empty string, the number 0, or an "
Laurent Le Brunfaf78412015-07-28 16:13:00 +00001016 + "empty collection. Otherwise, it returns True.",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001017 mandatoryPositionals = {@Param(name = "x", doc = "The variable to convert.")})
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001018 private static BuiltinFunction bool = new BuiltinFunction("bool") {
1019 public Boolean invoke(Object x) throws EvalException {
1020 return EvalUtils.toBoolean(x);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001021 }
1022 };
1023
Laurent Le Brunf4648de2015-05-07 14:00:32 +00001024 @SkylarkSignature(name = "int", returnType = Integer.class, doc = "Converts a value to int. "
1025 + "If the argument is a string, it is converted using base 10 and raises an error if the "
1026 + "conversion fails. If the argument is a bool, it returns 0 (False) or 1 (True). "
1027 + "If the argument is an int, it is simply returned."
Laurent Le Brun0c44aa42015-04-02 11:32:47 +00001028 + "<pre class=\"language-python\">int(\"123\") == 123</pre>",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001029 mandatoryPositionals = {
Laurent Le Brunf4648de2015-05-07 14:00:32 +00001030 @Param(name = "x", type = Object.class, doc = "The string to convert.")},
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001031 useLocation = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001032 private static BuiltinFunction int_ = new BuiltinFunction("int") {
Laurent Le Brunf4648de2015-05-07 14:00:32 +00001033 public Integer invoke(Object x, Location loc) throws EvalException {
1034 if (x instanceof Boolean) {
1035 return ((Boolean) x).booleanValue() ? 1 : 0;
1036 } else if (x instanceof Integer) {
1037 return (Integer) x;
1038 } else if (x instanceof String) {
1039 try {
1040 return Integer.parseInt((String) x);
1041 } catch (NumberFormatException e) {
1042 throw new EvalException(loc,
Francois-Rene Rideaud61f5312015-06-13 03:34:47 +00001043 "invalid literal for int(): " + Printer.repr(x));
Laurent Le Brunf4648de2015-05-07 14:00:32 +00001044 }
1045 } else {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001046 throw new EvalException(loc,
Francois-Rene Rideau8d5cce32015-06-16 23:12:04 +00001047 Printer.format("%r is not of type string or int or bool", x));
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001048 }
1049 }
1050 };
Laurent Le Brun0c44aa42015-04-02 11:32:47 +00001051
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001052 @SkylarkSignature(name = "struct", returnType = SkylarkClassObject.class, doc =
Laurent Le Brunfaf78412015-07-28 16:13:00 +00001053 "Creates an immutable struct using the keyword arguments as attributes. It is used to group "
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001054 + "multiple values together.Example:<br>"
Laurent Le Brun9d27a012015-03-31 12:28:02 +00001055 + "<pre class=\"language-python\">s = struct(x = 2, y = 3)\n"
Laurent Le Brunfaf78412015-07-28 16:13:00 +00001056 + "return s.x + getattr(s, \"y\") # returns 5</pre>",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001057 extraKeywords = {
Laurent Le Brunfaf78412015-07-28 16:13:00 +00001058 @Param(name = "kwargs", doc = "the struct attributes")},
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001059 useLocation = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001060 private static BuiltinFunction struct = new BuiltinFunction("struct") {
1061 @SuppressWarnings("unchecked")
1062 public SkylarkClassObject invoke(Map<String, Object> kwargs, Location loc)
1063 throws EvalException, InterruptedException {
1064 return new SkylarkClassObject(kwargs, loc);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001065 }
1066 };
1067
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001068 @SkylarkSignature(name = "set", returnType = SkylarkNestedSet.class,
David Chen24f2d992015-08-17 17:25:46 +00001069 doc = "Creates a <a href=\"set.html\">set</a> from the <code>items</code>."
Laurent Le Brun7ace1f22015-04-09 13:54:21 +00001070 + " The set supports nesting other sets of the same element"
1071 + " type in it. A desired iteration order can also be specified.<br>"
1072 + " Examples:<br><pre class=\"language-python\">set([\"a\", \"b\"])\n"
Laszlo Csomoraded1c22015-02-16 16:57:12 +00001073 + "set([1, 2, 3], order=\"compile\")</pre>",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001074 optionalPositionals = {
1075 @Param(name = "items", type = Object.class, defaultValue = "[]",
1076 doc = "The items to initialize the set with. May contain both standalone items "
1077 + "and other sets."),
1078 @Param(name = "order", type = String.class, defaultValue = "\"stable\"",
1079 doc = "The ordering strategy for the set if it's nested, "
1080 + "possible values are: <code>stable</code> (default), <code>compile</code>, "
Laurent Le Brun54820742015-07-30 16:43:52 +00001081 + "<code>link</code> or <code>naive_link</code>. An explanation of the "
David Chen24f2d992015-08-17 17:25:46 +00001082 + "values can be found <a href=\"set.html\">here</a>.")},
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001083 useLocation = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001084 private static final BuiltinFunction set = new BuiltinFunction("set") {
1085 public SkylarkNestedSet invoke(Object items, String order,
1086 Location loc) throws EvalException, ConversionException {
Florian Weikertfa759e22015-06-09 12:18:46 +00001087 try {
1088 return new SkylarkNestedSet(Order.parse(order), items, loc);
1089 } catch (IllegalArgumentException ex) {
1090 throw new EvalException(loc, ex);
1091 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001092 }
1093 };
Florian Weikerta6dae6b2015-08-04 20:17:23 +00001094
1095 @SkylarkSignature(name = "dict", returnType = Map.class,
1096 doc =
1097 "Creates a <a href=\"#modules.dict\">dictionary</a> from an optional positional "
1098 + "argument and an optional set of keyword arguments. Values from the keyword argument "
1099 + "will overwrite values from the positional argument if a key appears multiple times. "
1100 + "Dictionaries are always sorted by their keys",
1101 optionalPositionals = {
1102 @Param(name = "args", type = Iterable.class, defaultValue = "[]",
1103 doc =
1104 "List of entries. Entries must be tuples or lists with exactly "
1105 + "two elements: key, value"),
1106 },
1107 extraKeywords = {@Param(name = "kwargs", doc = "Dictionary of additional entries.")},
1108 useLocation = true)
1109 private static final BuiltinFunction dict = new BuiltinFunction("dict") {
1110 @SuppressWarnings("unused")
1111 public Map<Object, Object> invoke(Iterable<Object> args, Map<Object, Object> kwargs,
1112 Location loc) throws EvalException, ConversionException {
1113 try {
1114 Map<Object, Object> result = new HashMap<>();
1115 List<Object> list = Type.OBJECT_LIST.convert(args, "dict(args)");
1116
1117 for (Object tuple : list) {
1118 List<Object> mapping = Type.OBJECT_LIST.convert(tuple, "dict(args)");
1119 int numElements = mapping.size();
1120
1121 if (numElements != 2) {
1122 throw new EvalException(
1123 location,
1124 String.format(
1125 "Tuple has length %d, but exactly two elements are required", numElements));
1126 }
1127 result.put(mapping.get(0), mapping.get(1));
1128 }
1129 result.putAll(kwargs);
1130 return result;
1131 } catch (IllegalArgumentException | ClassCastException | NullPointerException ex) {
1132 throw new EvalException(loc, ex);
1133 }
1134 }
1135 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001136
Florian Weikert49319f12015-07-07 17:03:42 +00001137 @SkylarkSignature(name = "union", objectType = SkylarkNestedSet.class,
1138 returnType = SkylarkNestedSet.class,
David Chen24f2d992015-08-17 17:25:46 +00001139 doc = "Creates a new <a href=\"set.html\">set</a> that contains both "
Florian Weikert08c07742015-06-09 15:06:38 +00001140 + "the input set as well as all additional elements.",
1141 mandatoryPositionals = {
1142 @Param(name = "input", type = SkylarkNestedSet.class, doc = "The input set"),
1143 @Param(name = "newElements", type = Iterable.class, doc = "The elements to be added")},
1144 useLocation = true)
1145 private static final BuiltinFunction union = new BuiltinFunction("union") {
1146 @SuppressWarnings("unused")
1147 public SkylarkNestedSet invoke(SkylarkNestedSet input, Iterable<Object> newElements,
1148 Location loc) throws EvalException {
1149 return new SkylarkNestedSet(input, newElements, loc);
1150 }
1151 };
Laurent Le Brun54820742015-07-30 16:43:52 +00001152
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +00001153 @SkylarkSignature(name = "enumerate", returnType = HackHackEitherList.class,
Laurent Le Brun9ba067d2015-05-22 13:55:23 +00001154 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 +00001155 + " the input list.\n<pre class=\"language-python\">"
Laurent Le Brun9ba067d2015-05-22 13:55:23 +00001156 + "enumerate([24, 21, 84]) == [(0, 24), (1, 21), (2, 84)]</pre>\n",
Laurent Le Brun54820742015-07-30 16:43:52 +00001157 mandatoryPositionals = {
1158 @Param(name = "list", type = HackHackEitherList.class, doc = "input list")
1159 },
1160 useLocation = true,
1161 useEnvironment = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001162 private static BuiltinFunction enumerate = new BuiltinFunction("enumerate") {
Laurent Le Brun54820742015-07-30 16:43:52 +00001163 public Object invoke(Object input, Location loc, Environment env)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001164 throws EvalException, ConversionException, InterruptedException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001165 int count = 0;
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001166 List<SkylarkList> result = Lists.newArrayList();
Laurent Le Brun54820742015-07-30 16:43:52 +00001167 for (Object obj : Type.OBJECT_LIST.convert(input, "input")) {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001168 result.add(SkylarkList.tuple(count, obj));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001169 count++;
1170 }
Laurent Le Brun54820742015-07-30 16:43:52 +00001171 return convert(result, env, loc);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001172 }
1173 };
1174
Laurent Le Brun54820742015-07-30 16:43:52 +00001175 @SkylarkSignature(name = "range", returnType = HackHackEitherList.class,
Laszlo Csomor52fb6a22015-02-17 09:46:49 +00001176 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 +01001177 + "<code>step</code> increment. If a single argument is provided, items will "
1178 + "range from 0 to that element."
Laurent Le Brun9d27a012015-03-31 12:28:02 +00001179 + "<pre class=\"language-python\">range(4) == [0, 1, 2, 3]\n"
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001180 + "range(3, 9, 2) == [3, 5, 7]\n"
1181 + "range(3, 0, -1) == [3, 2, 1]</pre>",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001182 mandatoryPositionals = {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001183 @Param(name = "start_or_stop", type = Integer.class,
1184 doc = "Value of the start element if stop is provided, "
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001185 + "otherwise value of stop and the actual start is 0"),
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001186 },
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001187 optionalPositionals = {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001188 @Param(name = "stop_or_none", type = Integer.class, noneable = true, defaultValue = "None",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001189 doc = "optional index of the first item <i>not</i> to be included in the "
1190 + "resulting list; generation of the list stops before <code>stop</code> is reached."),
Francois-Rene Rideau534dca12015-04-21 19:43:19 +00001191 @Param(name = "step", type = Integer.class, defaultValue = "1",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001192 doc = "The increment (default is 1). It may be negative.")},
Laurent Le Brun54820742015-07-30 16:43:52 +00001193 useLocation = true,
1194 useEnvironment = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001195 private static final BuiltinFunction range = new BuiltinFunction("range") {
Laurent Le Brun54820742015-07-30 16:43:52 +00001196 public Object invoke(Integer startOrStop, Object stopOrNone, Integer step, Location loc,
1197 Environment env)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001198 throws EvalException, ConversionException, InterruptedException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001199 int start;
1200 int stop;
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +00001201 if (stopOrNone == Runtime.NONE) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001202 start = 0;
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001203 stop = startOrStop;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001204 } else {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001205 start = startOrStop;
1206 stop = Type.INTEGER.convert(stopOrNone, "'stop' operand of 'range'");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001207 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001208 if (step == 0) {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001209 throw new EvalException(loc, "step cannot be 0");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001210 }
1211 List<Integer> result = Lists.newArrayList();
1212 if (step > 0) {
1213 while (start < stop) {
1214 result.add(start);
1215 start += step;
1216 }
1217 } else {
1218 while (start > stop) {
1219 result.add(start);
1220 start += step;
1221 }
1222 }
Laurent Le Brun54820742015-07-30 16:43:52 +00001223 return convert(result, env, loc);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001224 }
1225 };
1226
1227 /**
1228 * Returns a function-value implementing "select" (i.e. configurable attributes)
1229 * in the specified package context.
1230 */
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001231 @SkylarkSignature(name = "select",
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001232 doc = "Creates a SelectorValue from the dict parameter.",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001233 mandatoryPositionals = {
1234 @Param(name = "x", type = Map.class, doc = "The parameter to convert.")})
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001235 private static final BuiltinFunction select = new BuiltinFunction("select") {
1236 public Object invoke(Map<?, ?> dict) throws EvalException, InterruptedException {
1237 return SelectorList.of(new SelectorValue(dict));
1238 }
1239 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001240
1241 /**
1242 * Returns true if the object has a field of the given name, otherwise false.
1243 */
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001244 @SkylarkSignature(name = "hasattr", returnType = Boolean.class,
Laurent Le Brunfaf78412015-07-28 16:13:00 +00001245 doc = "Returns True if the object <code>x</code> has an attribute of the given "
1246 + "<code>name</code>, otherwise False. Example:<br>"
Laurent Le Brun9d27a012015-03-31 12:28:02 +00001247 + "<pre class=\"language-python\">hasattr(ctx.attr, \"myattr\")</pre>",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001248 mandatoryPositionals = {
Laurent Le Brunfaf78412015-07-28 16:13:00 +00001249 @Param(name = "x", doc = "The object to check."),
1250 @Param(name = "name", type = String.class, doc = "The name of the attribute.")},
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001251 useLocation = true, useEnvironment = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001252 private static final BuiltinFunction hasattr = new BuiltinFunction("hasattr") {
1253 public Boolean invoke(Object obj, String name,
1254 Location loc, Environment env) throws EvalException, ConversionException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001255 if (obj instanceof ClassObject && ((ClassObject) obj).getValue(name) != null) {
1256 return true;
1257 }
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +00001258 if (Runtime.getFunctionNames(obj.getClass()).contains(name)) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001259 return true;
1260 }
1261
1262 try {
1263 return FuncallExpression.getMethodNames(obj.getClass()).contains(name);
1264 } catch (ExecutionException e) {
1265 // This shouldn't happen
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001266 throw new EvalException(loc, e.getMessage());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001267 }
1268 }
1269 };
1270
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001271 @SkylarkSignature(name = "getattr",
Laurent Le Brun18d84992015-08-19 12:13:36 +00001272 doc = "Returns the struct's field of the given name if it exists. If not, it either returns "
1273 + "<code>default</code> (if specified) or raises an error. <code>getattr(x, \"foobar\")"
1274 + "</code> is equivalent to <code>x.foobar</code>."
Laurent Le Brun9d27a012015-03-31 12:28:02 +00001275 + "<pre class=\"language-python\">getattr(ctx.attr, \"myattr\")\n"
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001276 + "getattr(ctx.attr, \"myattr\", \"mydefault\")</pre>",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001277 mandatoryPositionals = {
Laurent Le Brunfaf78412015-07-28 16:13:00 +00001278 @Param(name = "x", doc = "The struct whose attribute is accessed."),
1279 @Param(name = "name", doc = "The name of the struct attribute.")},
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001280 optionalPositionals = {
Francois-Rene Rideau534dca12015-04-21 19:43:19 +00001281 @Param(name = "default", defaultValue = "None",
1282 doc = "The default value to return in case the struct "
Laurent Le Brunfaf78412015-07-28 16:13:00 +00001283 + "doesn't have an attribute of the given name.")},
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001284 useLocation = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001285 private static final BuiltinFunction getattr = new BuiltinFunction("getattr") {
1286 public Object invoke(Object obj, String name, Object defaultValue,
1287 Location loc) throws EvalException, ConversionException {
1288 Object result = DotExpression.eval(obj, name, loc);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001289 if (result == null) {
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +00001290 if (defaultValue != Runtime.NONE) {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001291 return defaultValue;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001292 } else {
Laurent Le Brunfaf78412015-07-28 16:13:00 +00001293 throw new EvalException(loc, Printer.format("Object of type '%s' has no attribute %r",
Francois-Rene Rideau8d5cce32015-06-16 23:12:04 +00001294 EvalUtils.getDataTypeName(obj), name));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001295 }
1296 }
1297 return result;
1298 }
1299 };
1300
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001301 @SkylarkSignature(name = "dir", returnType = SkylarkList.class,
Laurent Le Brunfaf78412015-07-28 16:13:00 +00001302 doc = "Returns a list strings: the names of the attributes and "
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001303 + "methods of the parameter object.",
Laurent Le Brunfaf78412015-07-28 16:13:00 +00001304 mandatoryPositionals = {@Param(name = "x", doc = "The object to check.")},
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001305 useLocation = true, useEnvironment = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001306 private static final BuiltinFunction dir = new BuiltinFunction("dir") {
1307 public SkylarkList invoke(Object object,
1308 Location loc, Environment env) throws EvalException, ConversionException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001309 // Order the fields alphabetically.
1310 Set<String> fields = new TreeSet<>();
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001311 if (object instanceof ClassObject) {
1312 fields.addAll(((ClassObject) object).getKeys());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001313 }
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +00001314 fields.addAll(Runtime.getFunctionNames(object.getClass()));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001315 try {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001316 fields.addAll(FuncallExpression.getMethodNames(object.getClass()));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001317 } catch (ExecutionException e) {
1318 // This shouldn't happen
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001319 throw new EvalException(loc, e.getMessage());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001320 }
1321 return SkylarkList.list(fields, String.class);
1322 }
1323 };
1324
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001325 @SkylarkSignature(name = "type", returnType = String.class,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001326 doc = "Returns the type name of its argument.",
Laurent Le Brunfaf78412015-07-28 16:13:00 +00001327 mandatoryPositionals = {@Param(name = "x", doc = "The object to check type of.")})
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001328 private static final BuiltinFunction type = new BuiltinFunction("type") {
1329 public String invoke(Object object) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001330 // There is no 'type' type in Skylark, so we return a string with the type name.
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001331 return EvalUtils.getDataTypeName(object, false);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001332 }
1333 };
1334
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001335 @SkylarkSignature(name = "fail",
Laurent Le Brunfaf78412015-07-28 16:13:00 +00001336 doc = "Raises an error that cannot be intercepted. It can be used anywhere, "
1337 + "both in the loading phase and in the analysis phase.",
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +00001338 returnType = Runtime.NoneType.class,
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001339 mandatoryPositionals = {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001340 @Param(name = "msg", type = String.class, doc = "Error message to display for the user")},
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001341 optionalPositionals = {
1342 @Param(name = "attr", type = String.class, noneable = true,
1343 defaultValue = "None",
Laurent Le Brunfaf78412015-07-28 16:13:00 +00001344 doc = "The name of the attribute that caused the error. This is used only for "
1345 + "error reporting.")},
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001346 useLocation = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001347 private static final BuiltinFunction fail = new BuiltinFunction("fail") {
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +00001348 public Runtime.NoneType invoke(String msg, Object attr,
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001349 Location loc) throws EvalException, ConversionException {
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +00001350 if (attr != Runtime.NONE) {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001351 msg = String.format("attribute %s: %s", attr, msg);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001352 }
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001353 throw new EvalException(loc, msg);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001354 }
1355 };
1356
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +00001357 @SkylarkSignature(name = "print", returnType = Runtime.NoneType.class,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001358 doc = "Prints <code>msg</code> to the console.",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001359 optionalNamedOnly = {
Francois-Rene Rideau534dca12015-04-21 19:43:19 +00001360 @Param(name = "sep", type = String.class, defaultValue = "' '",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001361 doc = "The separator string between the objects, default is space (\" \").")},
1362 // NB: as compared to Python3, we're missing optional named-only arguments 'end' and 'file'
1363 extraPositionals = {@Param(name = "args", doc = "The objects to print.")},
1364 useLocation = true, useEnvironment = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001365 private static final BuiltinFunction print = new BuiltinFunction("print") {
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +00001366 public Runtime.NoneType invoke(String sep, SkylarkList starargs,
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001367 Location loc, SkylarkEnvironment env) throws EvalException {
1368 String msg = Joiner.on(sep).join(Iterables.transform(starargs,
1369 new com.google.common.base.Function<Object, String>() {
1370 @Override
1371 public String apply(Object input) {
Francois-Rene Rideaud61f5312015-06-13 03:34:47 +00001372 return Printer.str(input);
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001373 }}));
1374 env.handleEvent(Event.warn(loc, msg));
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +00001375 return Runtime.NONE;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001376 }
1377 };
1378
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001379 @SkylarkSignature(name = "zip",
Googlerc60ec8c2015-03-23 14:20:18 +00001380 doc = "Returns a <code>list</code> of <code>tuple</code>s, where the i-th tuple contains "
1381 + "the i-th element from each of the argument sequences or iterables. The list has the "
1382 + "size of the shortest input. With a single iterable argument, it returns a list of "
1383 + "1-tuples. With no arguments, it returns an empty list. Examples:"
Laurent Le Brun9d27a012015-03-31 12:28:02 +00001384 + "<pre class=\"language-python\">"
Googlerc60ec8c2015-03-23 14:20:18 +00001385 + "zip() # == []\n"
1386 + "zip([1, 2]) # == [(1,), (2,)]\n"
1387 + "zip([1, 2], [3, 4]) # == [(1, 3), (2, 4)]\n"
1388 + "zip([1, 2], [3, 4, 5]) # == [(1, 3), (2, 4)]</pre>",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001389 extraPositionals = {@Param(name = "args", doc = "lists to zip")},
1390 returnType = SkylarkList.class, useLocation = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001391 private static final BuiltinFunction zip = new BuiltinFunction("zip") {
1392 public SkylarkList invoke(SkylarkList args, Location loc)
1393 throws EvalException, InterruptedException {
Googlerc60ec8c2015-03-23 14:20:18 +00001394 Iterator<?>[] iterators = new Iterator<?>[args.size()];
1395 for (int i = 0; i < args.size(); i++) {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001396 iterators[i] = EvalUtils.toIterable(args.get(i), loc).iterator();
Googlerc60ec8c2015-03-23 14:20:18 +00001397 }
1398 List<SkylarkList> result = new ArrayList<SkylarkList>();
1399 boolean allHasNext;
1400 do {
1401 allHasNext = !args.isEmpty();
1402 List<Object> elem = Lists.newArrayListWithExpectedSize(args.size());
1403 for (Iterator<?> iterator : iterators) {
1404 if (iterator.hasNext()) {
1405 elem.add(iterator.next());
1406 } else {
1407 allHasNext = false;
1408 }
1409 }
1410 if (allHasNext) {
1411 result.add(SkylarkList.tuple(elem));
1412 }
1413 } while (allHasNext);
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001414 return SkylarkList.list(result, loc);
Googlerc60ec8c2015-03-23 14:20:18 +00001415 }
1416 };
1417
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001418 /**
1419 * Skylark String module.
1420 */
1421 @SkylarkModule(name = "string", doc =
1422 "A language built-in type to support strings. "
Laurent Le Brun4848a652015-03-18 14:50:12 +00001423 + "Examples of string literals:<br>"
Laurent Le Brun9d27a012015-03-31 12:28:02 +00001424 + "<pre class=\"language-python\">a = 'abc\\ndef'\n"
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001425 + "b = \"ab'cd\"\n"
Laurent Le Brun4848a652015-03-18 14:50:12 +00001426 + "c = \"\"\"multiline string\"\"\"\n"
1427 + "\n"
1428 + "# Strings support slicing (negative index starts from the end):\n"
1429 + "x = \"hello\"[2:4] # \"ll\"\n"
1430 + "y = \"hello\"[1:-1] # \"ell\"\n"
1431 + "z = \"hello\"[:4] # \"hell\"</pre>"
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001432 + "Strings are iterable and support the <code>in</code> operator. Examples:<br>"
Laurent Le Brunfaf78412015-07-28 16:13:00 +00001433 + "<pre class=\"language-python\">\"bc\" in \"abcd\" # evaluates to True\n"
Laurent Le Brun18d84992015-08-19 12:13:36 +00001434 + "x = [s for s in \"abc\"] # x == [\"a\", \"b\", \"c\"]</pre>\n"
Laurent Le Brun9ba067d2015-05-22 13:55:23 +00001435 + "Implicit concatenation of strings is not allowed; use the <code>+</code> "
1436 + "operator instead.")
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +00001437 static final class StringModule {}
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001438
1439 /**
1440 * Skylark Dict module.
1441 */
1442 @SkylarkModule(name = "dict", doc =
1443 "A language built-in type to support dicts. "
1444 + "Example of dict literal:<br>"
Laurent Le Brun9d27a012015-03-31 12:28:02 +00001445 + "<pre class=\"language-python\">d = {\"a\": 2, \"b\": 5}</pre>"
1446 + "Use brackets to access elements:<br>"
1447 + "<pre class=\"language-python\">e = d[\"a\"] # e == 2</pre>"
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001448 + "Dicts support the <code>+</code> operator to concatenate two dicts. In case of multiple "
1449 + "keys the second one overrides the first one. Examples:<br>"
Laurent Le Brun9d27a012015-03-31 12:28:02 +00001450 + "<pre class=\"language-python\">"
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001451 + "d = {\"a\" : 1} + {\"b\" : 2} # d == {\"a\" : 1, \"b\" : 2}\n"
1452 + "d += {\"c\" : 3} # d == {\"a\" : 1, \"b\" : 2, \"c\" : 3}\n"
1453 + "d = d + {\"c\" : 5} # d == {\"a\" : 1, \"b\" : 2, \"c\" : 5}</pre>"
1454 + "Since the language doesn't have mutable objects <code>d[\"a\"] = 5</code> automatically "
1455 + "translates to <code>d = d + {\"a\" : 5}</code>.<br>"
Laurent Le Brun9d27a012015-03-31 12:28:02 +00001456 + "Iterating on a dict is equivalent to iterating on its keys (in sorted order).<br>"
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001457 + "Dicts support the <code>in</code> operator, testing membership in the keyset of the dict. "
1458 + "Example:<br>"
Laurent Le Brun9d27a012015-03-31 12:28:02 +00001459 + "<pre class=\"language-python\">\"a\" in {\"a\" : 2, \"b\" : 5} # evaluates as True"
1460 + "</pre>")
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +00001461 static final class DictModule {}
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001462
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +00001463 static final List<BaseFunction> buildGlobalFunctions = ImmutableList.<BaseFunction>of(
Laurent Le Brun2f71fd72015-08-11 13:35:12 +00001464 bool, dict, enumerate, int_, len, list, minus, range, repr, select, sorted, str, zip);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001465
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +00001466 static final List<BaseFunction> skylarkGlobalFunctions =
Francois-Rene Rideau95b0d0c2015-04-22 16:52:13 +00001467 ImmutableList.<BaseFunction>builder()
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +00001468 .addAll(buildGlobalFunctions)
Laurent Le Brun2f71fd72015-08-11 13:35:12 +00001469 .add(dir, fail, getattr, hasattr, print, set, struct, type)
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001470 .build();
1471
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +00001472
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001473 /**
1474 * Set up a given environment for supported class methods.
1475 */
1476 public static void setupMethodEnvironment(Environment env) {
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +00001477 setupMethodEnvironment(env, env.isSkylark() ? skylarkGlobalFunctions : buildGlobalFunctions);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001478 }
1479
Francois-Rene Rideau95b0d0c2015-04-22 16:52:13 +00001480 private static void setupMethodEnvironment(Environment env, Iterable<BaseFunction> functions) {
1481 for (BaseFunction function : functions) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001482 env.update(function.getName(), function);
1483 }
1484 }
1485
Laurent Le Brun89bdeaa2015-04-14 14:41:59 +00001486 /**
1487 * Collect global functions for the validation environment.
1488 */
Laurent Le Brun352b9da2015-04-16 14:59:59 +00001489 public static void setupValidationEnvironment(Set<String> builtIn) {
Francois-Rene Rideau95b0d0c2015-04-22 16:52:13 +00001490 for (BaseFunction function : skylarkGlobalFunctions) {
Laurent Le Brun352b9da2015-04-16 14:59:59 +00001491 builtIn.add(function.getName());
Laurent Le Brun89bdeaa2015-04-14 14:41:59 +00001492 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001493 }
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001494
1495 static {
1496 SkylarkSignatureProcessor.configureSkylarkFunctions(MethodLibrary.class);
1497 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001498}