blob: 83d954daf4a5058516952aa965d326675241e9bc [file] [log] [blame]
Damien Martin-Guillerezf88f4d82015-09-25 13:56:55 +00001// Copyright 2014 The Bazel Authors. All rights reserved.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01002//
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
laurentlb3d2a68c2017-06-30 00:32:04 +020017import static java.util.stream.Collectors.joining;
18
Laurent Le Brunad849742015-10-15 11:36:01 +000019import com.google.common.base.CharMatcher;
Francois-Rene Rideau93ed7f12015-10-20 15:39:33 +000020import com.google.common.base.Joiner;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010021import com.google.common.collect.ImmutableList;
Florian Weikertbc1ff692016-07-01 19:11:24 +000022import com.google.common.collect.ImmutableMap;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010023import com.google.common.collect.Lists;
Francois-Rene Rideauc673a822015-03-02 19:52:39 +000024import com.google.common.collect.Ordering;
Florian Weikertd5e33502015-12-14 12:06:10 +000025import com.google.devtools.build.lib.collect.nestedset.NestedSet;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010026import com.google.devtools.build.lib.events.Event;
27import com.google.devtools.build.lib.events.Location;
Damien Martin-Guillerez2ca9b722016-06-09 17:43:55 +000028import com.google.devtools.build.lib.skylarkinterface.Param;
John Field585d1a02015-12-16 16:03:52 +000029import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
Dmitry Lomov34cdae32016-06-28 16:13:35 +000030import com.google.devtools.build.lib.skylarkinterface.SkylarkModuleCategory;
John Field585d1a02015-12-16 16:03:52 +000031import com.google.devtools.build.lib.skylarkinterface.SkylarkSignature;
Vladimir Moskva7f0cd622017-02-16 13:48:37 +000032import com.google.devtools.build.lib.syntax.EvalUtils.ComparisonException;
Francois-Rene Rideau4e994102015-09-17 22:41:28 +000033import com.google.devtools.build.lib.syntax.SkylarkList.MutableList;
34import com.google.devtools.build.lib.syntax.SkylarkList.Tuple;
Lukacs Berkiffa73ad2015-09-18 11:40:12 +000035import com.google.devtools.build.lib.syntax.Type.ConversionException;
brandjondc2c5502017-12-07 14:30:04 -080036import java.util.ArrayDeque;
Googlerc60ec8c2015-03-23 14:20:18 +000037import java.util.ArrayList;
Googlerc60ec8c2015-03-23 14:20:18 +000038import java.util.Iterator;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010039import java.util.List;
40import java.util.Map;
Florian Weikert5e8752b2015-12-11 21:54:43 +000041import java.util.NoSuchElementException;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010042import java.util.Set;
43import java.util.TreeSet;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010044import java.util.regex.Matcher;
45import java.util.regex.Pattern;
brandjon12b23792017-09-05 21:39:37 +020046import javax.annotation.Nullable;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010047
Laurent Le Brunbd9576a2016-11-18 15:10:51 +000048/** A helper class containing built in functions for the Skylark language. */
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010049public class MethodLibrary {
50
51 private MethodLibrary() {}
52
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010053 // Emulate Python substring function
54 // It converts out of range indices, and never fails
Francois-Rene Rideau76023b92015-04-17 15:31:59 +000055 private static String pythonSubstring(String str, int start, Object end, String msg)
56 throws ConversionException {
57 if (start == 0 && EvalUtils.isNullOrNone(end)) {
58 return str;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010059 }
Jon Brandveinfab84872016-11-11 16:27:01 +000060 start = EvalUtils.clampRangeEndpoint(start, str.length());
Francois-Rene Rideau76023b92015-04-17 15:31:59 +000061 int stop;
62 if (EvalUtils.isNullOrNone(end)) {
63 stop = str.length();
64 } else {
Jon Brandveinfab84872016-11-11 16:27:01 +000065 stop = EvalUtils.clampRangeEndpoint(Type.INTEGER.convert(end, msg), str.length());
Francois-Rene Rideau76023b92015-04-17 15:31:59 +000066 }
67 if (start >= stop) {
68 return "";
69 }
70 return str.substring(start, stop);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010071 }
72
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +000073 // supported string methods
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010074
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +000075 @SkylarkSignature(name = "join", objectType = StringModule.class, returnType = String.class,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010076 doc = "Returns a string in which the string elements of the argument have been "
77 + "joined by this string as a separator. Example:<br>"
Laurent Le Brun9d27a012015-03-31 12:28:02 +000078 + "<pre class=\"language-python\">\"|\".join([\"a\", \"b\", \"c\"]) == \"a|b|c\"</pre>",
Damien Martin-Guillerez014388c2016-06-14 10:28:31 +000079 parameters = {
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +000080 @Param(name = "self", type = String.class, doc = "This string, a separator."),
Francois-Rene Rideau93ed7f12015-10-20 15:39:33 +000081 @Param(name = "elements", type = SkylarkList.class, doc = "The objects to join.")})
Laurent Le Brunb525bee2016-03-07 17:14:10 +000082 private static final BuiltinFunction join = new BuiltinFunction("join") {
Francois-Rene Rideauab049e02016-02-17 16:13:46 +000083 public String invoke(String self, SkylarkList<?> elements) throws ConversionException {
Francois-Rene Rideau93ed7f12015-10-20 15:39:33 +000084 return Joiner.on(self).join(elements);
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +000085 }
86 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010087
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +000088 @SkylarkSignature(name = "lower", objectType = StringModule.class, returnType = String.class,
89 doc = "Returns the lower case version of this string.",
Damien Martin-Guillerez014388c2016-06-14 10:28:31 +000090 parameters = {
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +000091 @Param(name = "self", type = String.class, doc = "This string, to convert to lower case.")})
Laurent Le Brunb525bee2016-03-07 17:14:10 +000092 private static final BuiltinFunction lower = new BuiltinFunction("lower") {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +000093 public String invoke(String self) {
94 return self.toLowerCase();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010095 }
96 };
97
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +000098 @SkylarkSignature(name = "upper", objectType = StringModule.class, returnType = String.class,
99 doc = "Returns the upper case version of this string.",
Damien Martin-Guillerez014388c2016-06-14 10:28:31 +0000100 parameters = {
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000101 @Param(name = "self", type = String.class, doc = "This string, to convert to upper case.")})
Laurent Le Brunb525bee2016-03-07 17:14:10 +0000102 private static final BuiltinFunction upper = new BuiltinFunction("upper") {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000103 public String invoke(String self) {
104 return self.toUpperCase();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100105 }
106 };
107
Jon Brandvein36ecf162017-01-02 18:43:42 +0000108 /**
109 * For consistency with Python we recognize the same whitespace characters as they do over the
110 * range 0x00-0xFF. See https://hg.python.org/cpython/file/3.6/Objects/unicodetype_db.h#l5738
111 * This list is a consequence of Unicode character information.
112 *
113 * Note that this differs from Python 2.7, which uses ctype.h#isspace(), and from
114 * java.lang.Character#isWhitespace(), which does not recognize U+00A0.
115 */
116 private static final String LATIN1_WHITESPACE = (
117 "\u0009"
118 + "\n"
119 + "\u000B"
120 + "\u000C"
121 + "\r"
122 + "\u001C"
123 + "\u001D"
124 + "\u001E"
125 + "\u001F"
126 + "\u0020"
127 + "\u0085"
128 + "\u00A0"
129 );
130
Laurent Le Brunad849742015-10-15 11:36:01 +0000131 private static String stringLStrip(String self, String chars) {
132 CharMatcher matcher = CharMatcher.anyOf(chars);
133 for (int i = 0; i < self.length(); i++) {
134 if (!matcher.matches(self.charAt(i))) {
135 return self.substring(i);
136 }
137 }
138 return ""; // All characters were stripped.
139 }
140
141 private static String stringRStrip(String self, String chars) {
142 CharMatcher matcher = CharMatcher.anyOf(chars);
143 for (int i = self.length() - 1; i >= 0; i--) {
144 if (!matcher.matches(self.charAt(i))) {
145 return self.substring(0, i + 1);
146 }
147 }
148 return ""; // All characters were stripped.
149 }
150
151 @SkylarkSignature(
152 name = "lstrip",
153 objectType = StringModule.class,
154 returnType = String.class,
155 doc =
156 "Returns a copy of the string where leading characters that appear in <code>chars</code>"
157 + "are removed."
158 + "<pre class=\"language-python\">"
159 + "\"abcba\".lstrip(\"ba\") == \"cba\""
David Chenf8e18b72016-09-09 14:46:48 +0000160 + "</pre>",
Damien Martin-Guillerez014388c2016-06-14 10:28:31 +0000161 parameters = {
John Caterd7928422017-01-03 20:06:59 +0000162 @Param(name = "self", type = String.class, doc = "This string."),
Laurent Le Brunad849742015-10-15 11:36:01 +0000163 @Param(
164 name = "chars",
165 type = String.class,
Jon Brandvein36ecf162017-01-02 18:43:42 +0000166 noneable = true,
John Caterd7928422017-01-03 20:06:59 +0000167 doc = "The characters to remove, or all whitespace if None.",
Jon Brandvein36ecf162017-01-02 18:43:42 +0000168 defaultValue = "None"
Laurent Le Brunad849742015-10-15 11:36:01 +0000169 )
170 }
171 )
Laurent Le Brunb525bee2016-03-07 17:14:10 +0000172 private static final BuiltinFunction lstrip =
Laurent Le Brunad849742015-10-15 11:36:01 +0000173 new BuiltinFunction("lstrip") {
Jon Brandvein36ecf162017-01-02 18:43:42 +0000174 public String invoke(String self, Object charsOrNone) {
175 String chars = charsOrNone != Runtime.NONE ? (String) charsOrNone : LATIN1_WHITESPACE;
Laurent Le Brunad849742015-10-15 11:36:01 +0000176 return stringLStrip(self, chars);
177 }
178 };
179
180 @SkylarkSignature(
181 name = "rstrip",
182 objectType = StringModule.class,
183 returnType = String.class,
184 doc =
185 "Returns a copy of the string where trailing characters that appear in <code>chars</code>"
186 + "are removed."
187 + "<pre class=\"language-python\">"
brandjone835a3f2017-04-10 14:55:13 +0000188 + "\"abcbaa\".rstrip(\"ab\") == \"abc\""
David Chenf8e18b72016-09-09 14:46:48 +0000189 + "</pre>",
Damien Martin-Guillerez014388c2016-06-14 10:28:31 +0000190 parameters = {
John Caterd7928422017-01-03 20:06:59 +0000191 @Param(name = "self", type = String.class, doc = "This string."),
Laurent Le Brunad849742015-10-15 11:36:01 +0000192 @Param(
John Caterd7928422017-01-03 20:06:59 +0000193 name = "chars",
194 type = String.class,
195 noneable = true,
196 doc = "The characters to remove, or all whitespace if None.",
197 defaultValue = "None"
198 )
Laurent Le Brunad849742015-10-15 11:36:01 +0000199 }
200 )
Laurent Le Brunb525bee2016-03-07 17:14:10 +0000201 private static final BuiltinFunction rstrip =
Laurent Le Brunad849742015-10-15 11:36:01 +0000202 new BuiltinFunction("rstrip") {
Jon Brandvein36ecf162017-01-02 18:43:42 +0000203 public String invoke(String self, Object charsOrNone) {
204 String chars = charsOrNone != Runtime.NONE ? (String) charsOrNone : LATIN1_WHITESPACE;
Laurent Le Brunad849742015-10-15 11:36:01 +0000205 return stringRStrip(self, chars);
206 }
207 };
208
209 @SkylarkSignature(
210 name = "strip",
211 objectType = StringModule.class,
212 returnType = String.class,
213 doc =
Googlerd2c226c2018-03-01 03:00:53 -0800214 "Returns a copy of the string where leading or trailing characters that appear in "
215 + "<code>chars</code> are removed."
Laurent Le Brunad849742015-10-15 11:36:01 +0000216 + "<pre class=\"language-python\">"
brandjone835a3f2017-04-10 14:55:13 +0000217 + "\"aabcbcbaa\".strip(\"ab\") == \"cbc\""
David Chenf8e18b72016-09-09 14:46:48 +0000218 + "</pre>",
Damien Martin-Guillerez014388c2016-06-14 10:28:31 +0000219 parameters = {
John Caterd7928422017-01-03 20:06:59 +0000220 @Param(name = "self", type = String.class, doc = "This string."),
Laurent Le Brunad849742015-10-15 11:36:01 +0000221 @Param(
John Caterd7928422017-01-03 20:06:59 +0000222 name = "chars",
223 type = String.class,
224 noneable = true,
225 doc = "The characters to remove, or all whitespace if None.",
226 defaultValue = "None"
227 )
Laurent Le Brunad849742015-10-15 11:36:01 +0000228 }
229 )
Laurent Le Brunb525bee2016-03-07 17:14:10 +0000230 private static final BuiltinFunction strip =
Laurent Le Brunad849742015-10-15 11:36:01 +0000231 new BuiltinFunction("strip") {
Jon Brandvein36ecf162017-01-02 18:43:42 +0000232 public String invoke(String self, Object charsOrNone) {
233 String chars = charsOrNone != Runtime.NONE ? (String) charsOrNone : LATIN1_WHITESPACE;
Laurent Le Brunad849742015-10-15 11:36:01 +0000234 return stringLStrip(stringRStrip(self, chars), chars);
235 }
236 };
237
Laurent Le Brune51a4d22016-10-11 18:04:16 +0000238 @SkylarkSignature(
239 name = "replace",
240 objectType = StringModule.class,
241 returnType = String.class,
242 doc =
243 "Returns a copy of the string in which the occurrences "
244 + "of <code>old</code> have been replaced with <code>new</code>, optionally "
245 + "restricting the number of replacements to <code>maxsplit</code>.",
246 parameters = {
247 @Param(name = "self", type = String.class, doc = "This string."),
248 @Param(name = "old", type = String.class, doc = "The string to be replaced."),
249 @Param(name = "new", type = String.class, doc = "The string to replace with."),
250 @Param(
251 name = "maxsplit",
252 type = Integer.class,
253 noneable = true,
254 defaultValue = "None",
255 doc = "The maximum number of replacements."
256 )
257 },
258 useLocation = true
259 )
260 private static final BuiltinFunction replace =
261 new BuiltinFunction("replace") {
262 public String invoke(
263 String self, String oldString, String newString, Object maxSplitO, Location loc)
264 throws EvalException {
265 StringBuffer sb = new StringBuffer();
266 Integer maxSplit =
267 Type.INTEGER.convertOptional(
268 maxSplitO, "'maxsplit' argument of 'replace'", /*label*/ null, Integer.MAX_VALUE);
269 try {
270 Matcher m = Pattern.compile(oldString, Pattern.LITERAL).matcher(self);
271 for (int i = 0; i < maxSplit && m.find(); i++) {
272 m.appendReplacement(sb, Matcher.quoteReplacement(newString));
273 }
274 m.appendTail(sb);
275 } catch (IllegalStateException e) {
276 throw new EvalException(loc, e.getMessage() + " in call to replace");
277 }
278 return sb.toString();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100279 }
Laurent Le Brune51a4d22016-10-11 18:04:16 +0000280 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100281
Laurent Le Brune51a4d22016-10-11 18:04:16 +0000282 @SkylarkSignature(
283 name = "split",
284 objectType = StringModule.class,
285 returnType = MutableList.class,
286 doc =
287 "Returns a list of all the words in the string, using <code>sep</code> as the separator, "
288 + "optionally limiting the number of splits to <code>maxsplit</code>.",
289 parameters = {
290 @Param(name = "self", type = String.class, doc = "This string."),
291 @Param(name = "sep", type = String.class, doc = "The string to split on."),
292 @Param(
293 name = "maxsplit",
294 type = Integer.class,
295 noneable = true,
296 defaultValue = "None",
297 doc = "The maximum number of splits."
298 )
299 },
300 useEnvironment = true,
301 useLocation = true
302 )
303 private static final BuiltinFunction split =
304 new BuiltinFunction("split") {
305 public MutableList<String> invoke(
306 String self, String sep, Object maxSplitO, Location loc, Environment env)
307 throws EvalException {
308 int maxSplit =
309 Type.INTEGER.convertOptional(
310 maxSplitO, "'split' argument of 'split'", /*label*/ null, -2);
311 // + 1 because the last result is the remainder. The default is -2 so that after +1,
312 // it becomes -1.
313 String[] ss = Pattern.compile(sep, Pattern.LITERAL).split(self, maxSplit + 1);
314 return MutableList.of(env, ss);
315 }
316 };
Laurent Le Brun54820742015-07-30 16:43:52 +0000317
Laurent Le Brune51a4d22016-10-11 18:04:16 +0000318 @SkylarkSignature(
319 name = "rsplit",
320 objectType = StringModule.class,
321 returnType = MutableList.class,
322 doc =
323 "Returns a list of all the words in the string, using <code>sep</code> as the separator, "
324 + "optionally limiting the number of splits to <code>maxsplit</code>. "
325 + "Except for splitting from the right, this method behaves like split().",
326 parameters = {
327 @Param(name = "self", type = String.class, doc = "This string."),
328 @Param(name = "sep", type = String.class, doc = "The string to split on."),
329 @Param(
330 name = "maxsplit",
331 type = Integer.class,
332 noneable = true,
333 defaultValue = "None",
334 doc = "The maximum number of splits."
335 )
336 },
337 useEnvironment = true,
338 useLocation = true
339 )
340 private static final BuiltinFunction rsplit =
341 new BuiltinFunction("rsplit") {
342 @SuppressWarnings("unused")
343 public MutableList<String> invoke(
344 String self, String sep, Object maxSplitO, Location loc, Environment env)
345 throws EvalException {
346 int maxSplit =
347 Type.INTEGER.convertOptional(maxSplitO, "'split' argument of 'split'", null, -1);
348 try {
349 return stringRSplit(self, sep, maxSplit, env);
350 } catch (IllegalArgumentException ex) {
351 throw new EvalException(loc, ex);
352 }
353 }
354 };
Laurent Le Brun54820742015-07-30 16:43:52 +0000355
Florian Weikert8e2e5fc2015-05-29 12:39:08 +0000356 /**
Laurent Le Brune51a4d22016-10-11 18:04:16 +0000357 * Splits the given string into a list of words, using {@code separator} as a delimiter.
Laurent Le Brun54820742015-07-30 16:43:52 +0000358 *
Florian Weikert8e2e5fc2015-05-29 12:39:08 +0000359 * <p>At most {@code maxSplits} will be performed, going from right to left.
360 *
361 * @param input The input string.
362 * @param separator The separator string.
363 * @param maxSplits The maximum number of splits. Negative values mean unlimited splits.
364 * @return A list of words
365 * @throws IllegalArgumentException
366 */
Francois-Rene Rideauab049e02016-02-17 16:13:46 +0000367 private static MutableList<String> stringRSplit(
Laurent Le Brune51a4d22016-10-11 18:04:16 +0000368 String input, String separator, int maxSplits, Environment env) {
Florian Weikert8e2e5fc2015-05-29 12:39:08 +0000369 if (separator.isEmpty()) {
370 throw new IllegalArgumentException("Empty separator");
371 }
372
373 if (maxSplits <= 0) {
374 maxSplits = Integer.MAX_VALUE;
375 }
376
brandjondc2c5502017-12-07 14:30:04 -0800377 ArrayDeque<String> result = new ArrayDeque<>();
Florian Weikerte741e8f2015-06-22 20:57:01 +0000378 String[] parts = input.split(Pattern.quote(separator), -1);
Florian Weikert8e2e5fc2015-05-29 12:39:08 +0000379 int sepLen = separator.length();
Laurent Le Brun54820742015-07-30 16:43:52 +0000380 int remainingLength = input.length();
Florian Weikert8e2e5fc2015-05-29 12:39:08 +0000381 int splitsSoFar = 0;
382
Laurent Le Brun54820742015-07-30 16:43:52 +0000383 // Copies parts from the array into the final list, starting at the end (because
384 // it's rsplit), as long as fewer than maxSplits splits are performed. The
385 // last spot in the list is reserved for the remaining string, whose length
Florian Weikert8e2e5fc2015-05-29 12:39:08 +0000386 // has to be tracked throughout the loop.
387 for (int pos = parts.length - 1; (pos >= 0) && (splitsSoFar < maxSplits); --pos) {
388 String current = parts[pos];
389 result.addFirst(current);
390
391 ++splitsSoFar;
392 remainingLength -= sepLen + current.length();
393 }
394
395 if (splitsSoFar == maxSplits && remainingLength >= 0) {
Laurent Le Brun54820742015-07-30 16:43:52 +0000396 result.addFirst(input.substring(0, remainingLength));
Florian Weikert8e2e5fc2015-05-29 12:39:08 +0000397 }
Laurent Le Brun54820742015-07-30 16:43:52 +0000398
michajloff50f282017-10-05 20:02:51 +0200399 return MutableList.copyOf(env, result);
Florian Weikert8e2e5fc2015-05-29 12:39:08 +0000400 }
Laurent Le Brun54820742015-07-30 16:43:52 +0000401
Googler7ad2f092015-05-28 11:04:47 +0000402 @SkylarkSignature(name = "partition", objectType = StringModule.class,
Vladimir Moskvaf2eacf02017-02-22 23:57:49 +0000403 returnType = Tuple.class,
Googler7ad2f092015-05-28 11:04:47 +0000404 doc = "Splits the input string at the first occurrence of the separator "
405 + "<code>sep</code> and returns the resulting partition as a three-element "
Vladimir Moskvaf2eacf02017-02-22 23:57:49 +0000406 + "tuple of the form (substring_before, separator, substring_after).",
Damien Martin-Guillerez014388c2016-06-14 10:28:31 +0000407 parameters = {
408 @Param(name = "self", type = String.class, doc = "This string."),
Googler7ad2f092015-05-28 11:04:47 +0000409 @Param(name = "sep", type = String.class,
laurentlbd1e564b2017-07-19 21:18:24 +0200410 defaultValue = "\" \"", doc = "The string to split on, default is space (\" \").")},
Googler7ad2f092015-05-28 11:04:47 +0000411 useEnvironment = true,
412 useLocation = true)
Laurent Le Brunb525bee2016-03-07 17:14:10 +0000413 private static final BuiltinFunction partition = new BuiltinFunction("partition") {
Googler7ad2f092015-05-28 11:04:47 +0000414 @SuppressWarnings("unused")
Vladimir Moskvaf2eacf02017-02-22 23:57:49 +0000415 public Tuple<String> invoke(String self, String sep, Location loc, Environment env)
Googler7ad2f092015-05-28 11:04:47 +0000416 throws EvalException {
Vladimir Moskvaf2eacf02017-02-22 23:57:49 +0000417 return partitionWrapper(self, sep, true, loc);
Googler7ad2f092015-05-28 11:04:47 +0000418 }
419 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100420
Googler7ad2f092015-05-28 11:04:47 +0000421 @SkylarkSignature(name = "rpartition", objectType = StringModule.class,
Vladimir Moskvaf2eacf02017-02-22 23:57:49 +0000422 returnType = Tuple.class,
Googler7ad2f092015-05-28 11:04:47 +0000423 doc = "Splits the input string at the last occurrence of the separator "
424 + "<code>sep</code> and returns the resulting partition as a three-element "
Vladimir Moskvaf2eacf02017-02-22 23:57:49 +0000425 + "tuple of the form (substring_before, separator, substring_after).",
Damien Martin-Guillerez014388c2016-06-14 10:28:31 +0000426 parameters = {
427 @Param(name = "self", type = String.class, doc = "This string."),
Googler7ad2f092015-05-28 11:04:47 +0000428 @Param(name = "sep", type = String.class,
laurentlbd1e564b2017-07-19 21:18:24 +0200429 defaultValue = "\" \"", doc = "The string to split on, default is space (\" \").")},
Googler7ad2f092015-05-28 11:04:47 +0000430 useEnvironment = true,
431 useLocation = true)
Laurent Le Brunb525bee2016-03-07 17:14:10 +0000432 private static final BuiltinFunction rpartition = new BuiltinFunction("rpartition") {
Googler7ad2f092015-05-28 11:04:47 +0000433 @SuppressWarnings("unused")
Vladimir Moskvaf2eacf02017-02-22 23:57:49 +0000434 public Tuple<String> invoke(String self, String sep, Location loc, Environment env)
Googler7ad2f092015-05-28 11:04:47 +0000435 throws EvalException {
Vladimir Moskvaf2eacf02017-02-22 23:57:49 +0000436 return partitionWrapper(self, sep, false, loc);
Googler7ad2f092015-05-28 11:04:47 +0000437 }
438 };
439
440 /**
Laurent Le Brun54820742015-07-30 16:43:52 +0000441 * Wraps the stringPartition() method and converts its results and exceptions
Googler7ad2f092015-05-28 11:04:47 +0000442 * to the expected types.
443 *
444 * @param self The input string
445 * @param separator The string to split on
Laurent Le Brun54820742015-07-30 16:43:52 +0000446 * @param forward A flag that controls whether the input string is split around
447 * the first ({@code true}) or last ({@code false}) occurrence of the separator.
Googler7ad2f092015-05-28 11:04:47 +0000448 * @param loc The location that is used for potential exceptions
449 * @return A list with three elements
450 */
Vladimir Moskvaf2eacf02017-02-22 23:57:49 +0000451 private static Tuple<String> partitionWrapper(
452 String self, String separator, boolean forward, Location loc) throws EvalException {
Googler7ad2f092015-05-28 11:04:47 +0000453 try {
Vladimir Moskvaf2eacf02017-02-22 23:57:49 +0000454 return Tuple.copyOf(stringPartition(self, separator, forward));
Googler7ad2f092015-05-28 11:04:47 +0000455 } catch (IllegalArgumentException ex) {
456 throw new EvalException(loc, ex);
457 }
458 }
459
460 /**
Laurent Le Brune51a4d22016-10-11 18:04:16 +0000461 * Splits the input string at the {first|last} occurrence of the given separator and returns the
462 * resulting partition as a three-tuple of Strings, contained in a {@code MutableList}.
Laurent Le Brun54820742015-07-30 16:43:52 +0000463 *
Laurent Le Brune51a4d22016-10-11 18:04:16 +0000464 * <p>If the input string does not contain the separator, the tuple will consist of the original
465 * input string and two empty strings.
Laurent Le Brun54820742015-07-30 16:43:52 +0000466 *
Laurent Le Brune51a4d22016-10-11 18:04:16 +0000467 * <p>This method emulates the behavior of Python's str.partition() and str.rpartition(),
468 * depending on the value of the {@code forward} flag.
Laurent Le Brun54820742015-07-30 16:43:52 +0000469 *
Googler7ad2f092015-05-28 11:04:47 +0000470 * @param input The input string
471 * @param separator The string to split on
Laurent Le Brune51a4d22016-10-11 18:04:16 +0000472 * @param forward A flag that controls whether the input string is split around the first ({@code
473 * true}) or last ({@code false}) occurrence of the separator.
Laurent Le Brun54820742015-07-30 16:43:52 +0000474 * @return A three-tuple (List) of the form [part_before_separator, separator,
Laurent Le Brune51a4d22016-10-11 18:04:16 +0000475 * part_after_separator].
Googler7ad2f092015-05-28 11:04:47 +0000476 */
Laurent Le Brune51a4d22016-10-11 18:04:16 +0000477 private static List<String> stringPartition(String input, String separator, boolean forward) {
Googler7ad2f092015-05-28 11:04:47 +0000478 if (separator.isEmpty()) {
479 throw new IllegalArgumentException("Empty separator");
480 }
481
482 int partitionSize = 3;
483 ArrayList<String> result = new ArrayList<>(partitionSize);
484 int pos = forward ? input.indexOf(separator) : input.lastIndexOf(separator);
485
486 if (pos < 0) {
487 for (int i = 0; i < partitionSize; ++i) {
488 result.add("");
489 }
490
491 // Following Python's implementation of str.partition() and str.rpartition(),
492 // the input string is copied to either the first or the last position in the
493 // list, depending on the value of the forward flag.
494 result.set(forward ? 0 : partitionSize - 1, input);
495 } else {
496 result.add(input.substring(0, pos));
497 result.add(separator);
498
499 // pos + sep.length() is at most equal to input.length(). This worst-case
500 // happens when the separator is at the end of the input string. However,
501 // substring() will return an empty string in this scenario, thus making
502 // any additional safety checks obsolete.
503 result.add(input.substring(pos + separator.length()));
504 }
505
506 return result;
507 }
Laurent Le Brun54820742015-07-30 16:43:52 +0000508
Laurent Le Brunb4114cc2015-08-26 14:53:37 +0000509 @SkylarkSignature(
510 name = "capitalize",
511 objectType = StringModule.class,
512 returnType = String.class,
513 doc =
Mark Schaller86eeb8c2015-09-14 14:36:37 +0000514 "Returns a copy of the string with its first character capitalized and the rest "
Laurent Le Brunb4114cc2015-08-26 14:53:37 +0000515 + "lowercased. This method does not support non-ascii characters.",
Damien Martin-Guillerez014388c2016-06-14 10:28:31 +0000516 parameters = {@Param(name = "self", type = String.class, doc = "This string.")}
Laurent Le Brunb4114cc2015-08-26 14:53:37 +0000517 )
Laurent Le Brunb525bee2016-03-07 17:14:10 +0000518 private static final BuiltinFunction capitalize =
Laurent Le Brunb4114cc2015-08-26 14:53:37 +0000519 new BuiltinFunction("capitalize") {
520 @SuppressWarnings("unused")
521 public String invoke(String self) throws EvalException {
522 if (self.isEmpty()) {
523 return self;
524 }
525 return Character.toUpperCase(self.charAt(0)) + self.substring(1).toLowerCase();
526 }
527 };
528
Florian Weikert006bf4f2015-08-03 12:28:35 +0000529 @SkylarkSignature(name = "title", objectType = StringModule.class,
530 returnType = String.class,
531 doc =
532 "Converts the input string into title case, i.e. every word starts with an "
533 + "uppercase letter while the remaining letters are lowercase. In this "
534 + "context, a word means strictly a sequence of letters. This method does "
535 + "not support supplementary Unicode characters.",
Damien Martin-Guillerez014388c2016-06-14 10:28:31 +0000536 parameters = {
Florian Weikert006bf4f2015-08-03 12:28:35 +0000537 @Param(name = "self", type = String.class, doc = "This string.")})
Laurent Le Brunb525bee2016-03-07 17:14:10 +0000538 private static final BuiltinFunction title = new BuiltinFunction("title") {
Florian Weikert006bf4f2015-08-03 12:28:35 +0000539 @SuppressWarnings("unused")
540 public String invoke(String self) throws EvalException {
541 char[] data = self.toCharArray();
542 boolean previousWasLetter = false;
543
544 for (int pos = 0; pos < data.length; ++pos) {
545 char current = data[pos];
546 boolean currentIsLetter = Character.isLetter(current);
547
548 if (currentIsLetter) {
549 if (previousWasLetter && Character.isUpperCase(current)) {
550 data[pos] = Character.toLowerCase(current);
551 } else if (!previousWasLetter && Character.isLowerCase(current)) {
552 data[pos] = Character.toUpperCase(current);
553 }
554 }
555 previousWasLetter = currentIsLetter;
556 }
557
558 return new String(data);
559 }
560 };
561
Francois-Rene Rideau76023b92015-04-17 15:31:59 +0000562 /**
563 * Common implementation for find, rfind, index, rindex.
564 * @param forward true if we want to return the last matching index.
565 */
566 private static int stringFind(boolean forward,
567 String self, String sub, int start, Object end, String msg)
568 throws ConversionException {
569 String substr = pythonSubstring(self, start, end, msg);
570 int subpos = forward ? substr.indexOf(sub) : substr.lastIndexOf(sub);
Jon Brandveinfab84872016-11-11 16:27:01 +0000571 start = EvalUtils.clampRangeEndpoint(start, self.length());
Francois-Rene Rideau76023b92015-04-17 15:31:59 +0000572 return subpos < 0 ? subpos : subpos + start;
573 }
574
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000575 @SkylarkSignature(name = "rfind", objectType = StringModule.class, returnType = Integer.class,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100576 doc = "Returns the last index where <code>sub</code> is found, "
577 + "or -1 if no such index exists, optionally restricting to "
578 + "[<code>start</code>:<code>end</code>], "
579 + "<code>start</code> being inclusive and <code>end</code> being exclusive.",
Damien Martin-Guillerez014388c2016-06-14 10:28:31 +0000580 parameters = {
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000581 @Param(name = "self", type = String.class, doc = "This string."),
Damien Martin-Guillerez014388c2016-06-14 10:28:31 +0000582 @Param(name = "sub", type = String.class, doc = "The substring to find."),
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000583 @Param(name = "start", type = Integer.class, defaultValue = "0",
584 doc = "Restrict to search from this position."),
585 @Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None",
586 doc = "optional position before which to restrict to search.")})
Laurent Le Brunb525bee2016-03-07 17:14:10 +0000587 private static final BuiltinFunction rfind = new BuiltinFunction("rfind") {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000588 public Integer invoke(String self, String sub, Integer start, Object end)
589 throws ConversionException {
590 return stringFind(false, self, sub, start, end, "'end' argument to rfind");
591 }
592 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100593
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000594 @SkylarkSignature(name = "find", objectType = StringModule.class, returnType = Integer.class,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100595 doc = "Returns the first index where <code>sub</code> is found, "
596 + "or -1 if no such index exists, optionally restricting to "
597 + "[<code>start</code>:<code>end]</code>, "
598 + "<code>start</code> being inclusive and <code>end</code> being exclusive.",
Damien Martin-Guillerez014388c2016-06-14 10:28:31 +0000599 parameters = {
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000600 @Param(name = "self", type = String.class, doc = "This string."),
Damien Martin-Guillerez014388c2016-06-14 10:28:31 +0000601 @Param(name = "sub", type = String.class, doc = "The substring to find."),
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000602 @Param(name = "start", type = Integer.class, defaultValue = "0",
603 doc = "Restrict to search from this position."),
604 @Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None",
605 doc = "optional position before which to restrict to search.")})
Laurent Le Brunb525bee2016-03-07 17:14:10 +0000606 private static final BuiltinFunction find = new BuiltinFunction("find") {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000607 public Integer invoke(String self, String sub, Integer start, Object end)
608 throws ConversionException {
609 return stringFind(true, self, sub, start, end, "'end' argument to find");
610 }
611 };
Laurent Le Brun4e116c72015-03-23 13:48:50 +0000612
Laurent Le Brune51a4d22016-10-11 18:04:16 +0000613 @SkylarkSignature(
614 name = "rindex",
615 objectType = StringModule.class,
616 returnType = Integer.class,
617 doc =
618 "Returns the last index where <code>sub</code> is found, "
619 + "or raises an error if no such index exists, optionally restricting to "
620 + "[<code>start</code>:<code>end</code>], "
621 + "<code>start</code> being inclusive and <code>end</code> being exclusive.",
622 parameters = {
623 @Param(name = "self", type = String.class, doc = "This string."),
624 @Param(name = "sub", type = String.class, doc = "The substring to find."),
625 @Param(
626 name = "start",
627 type = Integer.class,
628 defaultValue = "0",
629 doc = "Restrict to search from this position."
630 ),
631 @Param(
632 name = "end",
633 type = Integer.class,
634 noneable = true,
635 defaultValue = "None",
636 doc = "optional position before which to restrict to search."
637 )
638 },
639 useLocation = true
640 )
641 private static final BuiltinFunction rindex =
642 new BuiltinFunction("rindex") {
643 public Integer invoke(String self, String sub, Integer start, Object end, Location loc)
644 throws EvalException {
645 int res = stringFind(false, self, sub, start, end, "'end' argument to rindex");
646 if (res < 0) {
647 throw new EvalException(loc, Printer.format("substring %r not found in %r", sub, self));
648 }
649 return res;
650 }
651 };
Laurent Le Brun4e116c72015-03-23 13:48:50 +0000652
Laurent Le Brune51a4d22016-10-11 18:04:16 +0000653 @SkylarkSignature(
654 name = "index",
655 objectType = StringModule.class,
656 returnType = Integer.class,
657 doc =
658 "Returns the first index where <code>sub</code> is found, "
659 + "or raises an error if no such index exists, optionally restricting to "
660 + "[<code>start</code>:<code>end]</code>, "
661 + "<code>start</code> being inclusive and <code>end</code> being exclusive.",
662 parameters = {
663 @Param(name = "self", type = String.class, doc = "This string."),
664 @Param(name = "sub", type = String.class, doc = "The substring to find."),
665 @Param(
666 name = "start",
667 type = Integer.class,
668 defaultValue = "0",
669 doc = "Restrict to search from this position."
670 ),
671 @Param(
672 name = "end",
673 type = Integer.class,
674 noneable = true,
675 defaultValue = "None",
676 doc = "optional position before which to restrict to search."
677 )
678 },
679 useLocation = true
680 )
681 private static final BuiltinFunction index =
682 new BuiltinFunction("index") {
683 public Integer invoke(String self, String sub, Integer start, Object end, Location loc)
684 throws EvalException {
685 int res = stringFind(true, self, sub, start, end, "'end' argument to index");
686 if (res < 0) {
687 throw new EvalException(loc, Printer.format("substring %r not found in %r", sub, self));
688 }
689 return res;
690 }
691 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100692
Florian Weikertcb8f2782015-12-10 23:30:23 +0000693 @SkylarkSignature(name = "splitlines", objectType = StringModule.class,
Dmitry Lomov5fd7da52016-09-05 09:44:48 +0000694 returnType = SkylarkList.class,
Florian Weikertcb8f2782015-12-10 23:30:23 +0000695 doc =
696 "Splits the string at line boundaries ('\\n', '\\r\\n', '\\r') "
697 + "and returns the result as a list.",
Damien Martin-Guillerez014388c2016-06-14 10:28:31 +0000698 parameters = {
699 @Param(name = "self", type = String.class, doc = "This string."),
Florian Weikertcb8f2782015-12-10 23:30:23 +0000700 @Param(name = "keepends", type = Boolean.class, defaultValue = "False",
701 doc = "Whether the line breaks should be included in the resulting list.")})
Laurent Le Brunb525bee2016-03-07 17:14:10 +0000702 private static final BuiltinFunction splitLines = new BuiltinFunction("splitlines") {
Florian Weikertcb8f2782015-12-10 23:30:23 +0000703 @SuppressWarnings("unused")
Dmitry Lomov5fd7da52016-09-05 09:44:48 +0000704 public SkylarkList<String> invoke(String self, Boolean keepEnds) throws EvalException {
Florian Weikertcb8f2782015-12-10 23:30:23 +0000705 List<String> result = new ArrayList<>();
706 Matcher matcher = SPLIT_LINES_PATTERN.matcher(self);
707 while (matcher.find()) {
708 String line = matcher.group("line");
709 String lineBreak = matcher.group("break");
710 boolean trailingBreak = lineBreak.isEmpty();
711 if (line.isEmpty() && trailingBreak) {
712 break;
713 }
714 if (keepEnds && !trailingBreak) {
715 result.add(line + lineBreak);
716 } else {
717 result.add(line);
718 }
719 }
Dmitry Lomov5fd7da52016-09-05 09:44:48 +0000720 return SkylarkList.createImmutable(result);
Florian Weikertcb8f2782015-12-10 23:30:23 +0000721 }
722 };
723
724 private static final Pattern SPLIT_LINES_PATTERN =
725 Pattern.compile("(?<line>.*)(?<break>(\\r\\n|\\r|\\n)?)");
726
Googler8e8fa052015-09-03 18:36:33 +0000727 @SkylarkSignature(name = "isalpha", objectType = StringModule.class, returnType = Boolean.class,
Florian Weikert532d3ba2015-12-17 10:36:45 +0000728 doc = "Returns True if all characters in the string are alphabetic ([a-zA-Z]) and there is "
729 + "at least one character.",
Damien Martin-Guillerez014388c2016-06-14 10:28:31 +0000730 parameters = {
Googler8e8fa052015-09-03 18:36:33 +0000731 @Param(name = "self", type = String.class, doc = "This string.")})
Laurent Le Brunb525bee2016-03-07 17:14:10 +0000732 private static final BuiltinFunction isalpha = new BuiltinFunction("isalpha") {
Florian Weikert532d3ba2015-12-17 10:36:45 +0000733 @SuppressWarnings("unused") // Called via Reflection
Googler8e8fa052015-09-03 18:36:33 +0000734 public Boolean invoke(String self) throws EvalException {
Florian Weikert532d3ba2015-12-17 10:36:45 +0000735 return MethodLibrary.matches(self, MethodLibrary.ALPHA, false);
Florian Weikertd03485f2015-12-15 16:50:40 +0000736 }
737 };
738
Florian Weikert532d3ba2015-12-17 10:36:45 +0000739 @SkylarkSignature(name = "isalnum", objectType = StringModule.class, returnType = Boolean.class,
740 doc =
741 "Returns True if all characters in the string are alphanumeric ([a-zA-Z0-9]) and there is "
742 + "at least one character.",
Damien Martin-Guillerez014388c2016-06-14 10:28:31 +0000743 parameters = {@Param(name = "self", type = String.class, doc = "This string.")})
Laurent Le Brunb525bee2016-03-07 17:14:10 +0000744 private static final BuiltinFunction isAlnum = new BuiltinFunction("isalnum") {
Florian Weikert532d3ba2015-12-17 10:36:45 +0000745 @SuppressWarnings("unused") // Called via Reflection
746 public Boolean invoke(String self) throws EvalException {
747 return MethodLibrary.matches(self, MethodLibrary.ALNUM, false);
748 }
749 };
750
751 @SkylarkSignature(name = "isdigit", objectType = StringModule.class, returnType = Boolean.class,
752 doc =
753 "Returns True if all characters in the string are digits ([0-9]) and there is "
754 + "at least one character.",
Damien Martin-Guillerez014388c2016-06-14 10:28:31 +0000755 parameters = {@Param(name = "self", type = String.class, doc = "This string.")})
Laurent Le Brunb525bee2016-03-07 17:14:10 +0000756 private static final BuiltinFunction isDigit = new BuiltinFunction("isdigit") {
Florian Weikert532d3ba2015-12-17 10:36:45 +0000757 @SuppressWarnings("unused") // Called via Reflection
758 public Boolean invoke(String self) throws EvalException {
759 return MethodLibrary.matches(self, MethodLibrary.DIGIT, false);
760 }
761 };
762
763 @SkylarkSignature(name = "isspace", objectType = StringModule.class, returnType = Boolean.class,
764 doc =
765 "Returns True if all characters are white space characters and the string "
766 + "contains at least one character.",
Damien Martin-Guillerez014388c2016-06-14 10:28:31 +0000767 parameters = {@Param(name = "self", type = String.class, doc = "This string.")})
Laurent Le Brunb525bee2016-03-07 17:14:10 +0000768 private static final BuiltinFunction isSpace = new BuiltinFunction("isspace") {
Florian Weikert532d3ba2015-12-17 10:36:45 +0000769 @SuppressWarnings("unused") // Called via Reflection
770 public Boolean invoke(String self) throws EvalException {
771 return MethodLibrary.matches(self, MethodLibrary.SPACE, false);
772 }
773 };
774
775 @SkylarkSignature(name = "islower", objectType = StringModule.class, returnType = Boolean.class,
776 doc =
777 "Returns True if all cased characters in the string are lowercase and there is "
778 + "at least one character.",
Damien Martin-Guillerez014388c2016-06-14 10:28:31 +0000779 parameters = {@Param(name = "self", type = String.class, doc = "This string.")})
Laurent Le Brunb525bee2016-03-07 17:14:10 +0000780 private static final BuiltinFunction isLower = new BuiltinFunction("islower") {
Florian Weikert532d3ba2015-12-17 10:36:45 +0000781 @SuppressWarnings("unused") // Called via Reflection
782 public Boolean invoke(String self) throws EvalException {
783 // Python also accepts non-cased characters, so we cannot use LOWER.
784 return MethodLibrary.matches(self, MethodLibrary.UPPER.negate(), true);
785 }
786 };
787
788 @SkylarkSignature(name = "isupper", objectType = StringModule.class, returnType = Boolean.class,
789 doc =
790 "Returns True if all cased characters in the string are uppercase and there is "
791 + "at least one character.",
Damien Martin-Guillerez014388c2016-06-14 10:28:31 +0000792 parameters = {@Param(name = "self", type = String.class, doc = "This string.")})
Laurent Le Brunb525bee2016-03-07 17:14:10 +0000793 private static final BuiltinFunction isUpper = new BuiltinFunction("isupper") {
Florian Weikert532d3ba2015-12-17 10:36:45 +0000794 @SuppressWarnings("unused") // Called via Reflection
795 public Boolean invoke(String self) throws EvalException {
796 // Python also accepts non-cased characters, so we cannot use UPPER.
797 return MethodLibrary.matches(self, MethodLibrary.LOWER.negate(), true);
798 }
799 };
800
801 @SkylarkSignature(name = "istitle", objectType = StringModule.class, returnType = Boolean.class,
802 doc =
803 "Returns True if the string is in title case and it contains at least one character. "
804 + "This means that every uppercase character must follow an uncased one (e.g. whitespace) "
805 + "and every lowercase character must follow a cased one (e.g. uppercase or lowercase).",
Damien Martin-Guillerez014388c2016-06-14 10:28:31 +0000806 parameters = {@Param(name = "self", type = String.class, doc = "This string.")})
Laurent Le Brunb525bee2016-03-07 17:14:10 +0000807 private static final BuiltinFunction isTitle = new BuiltinFunction("istitle") {
Florian Weikert532d3ba2015-12-17 10:36:45 +0000808 @SuppressWarnings("unused") // Called via Reflection
809 public Boolean invoke(String self) throws EvalException {
810 if (self.isEmpty()) {
811 return false;
812 }
813 // From the Python documentation: "uppercase characters may only follow uncased characters
814 // and lowercase characters only cased ones".
815 char[] data = self.toCharArray();
Googlere92bfe52016-03-07 19:32:24 +0000816 CharMatcher matcher = CharMatcher.any();
Florian Weikert532d3ba2015-12-17 10:36:45 +0000817 char leftMostCased = ' ';
818 for (int pos = data.length - 1; pos >= 0; --pos) {
819 char current = data[pos];
820 // 1. Check condition that was determined by the right neighbor.
821 if (!matcher.matches(current)) {
822 return false;
823 }
824 // 2. Determine condition for the left neighbor.
825 if (LOWER.matches(current)) {
826 matcher = CASED;
827 } else if (UPPER.matches(current)) {
828 matcher = CASED.negate();
829 } else {
Googlere92bfe52016-03-07 19:32:24 +0000830 matcher = CharMatcher.any();
Florian Weikert532d3ba2015-12-17 10:36:45 +0000831 }
832 // 3. Store character if it is cased.
833 if (CASED.matches(current)) {
834 leftMostCased = current;
835 }
836 }
837 // The leftmost cased letter must be uppercase. If leftMostCased is not a cased letter here,
838 // then the string doesn't have any cased letter, so UPPER.test will return false.
839 return UPPER.matches(leftMostCased);
840 }
841 };
842
843 private static boolean matches(
844 String str, CharMatcher matcher, boolean requiresAtLeastOneCasedLetter) {
845 if (str.isEmpty()) {
846 return false;
847 } else if (!requiresAtLeastOneCasedLetter) {
848 return matcher.matchesAllOf(str);
849 }
850 int casedLetters = 0;
851 for (char current : str.toCharArray()) {
852 if (!matcher.matches(current)) {
853 return false;
854 } else if (requiresAtLeastOneCasedLetter && CASED.matches(current)) {
855 ++casedLetters;
856 }
857 }
858 return casedLetters > 0;
859 }
860
861 private static final CharMatcher DIGIT = CharMatcher.javaDigit();
862 private static final CharMatcher LOWER = CharMatcher.inRange('a', 'z');
863 private static final CharMatcher UPPER = CharMatcher.inRange('A', 'Z');
864 private static final CharMatcher ALPHA = LOWER.or(UPPER);
865 private static final CharMatcher ALNUM = ALPHA.or(DIGIT);
866 private static final CharMatcher CASED = ALPHA;
Googlere92bfe52016-03-07 19:32:24 +0000867 private static final CharMatcher SPACE = CharMatcher.whitespace();
Florian Weikert532d3ba2015-12-17 10:36:45 +0000868
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000869 @SkylarkSignature(name = "count", objectType = StringModule.class, returnType = Integer.class,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100870 doc = "Returns the number of (non-overlapping) occurrences of substring <code>sub</code> in "
871 + "string, optionally restricting to [<code>start</code>:<code>end</code>], "
872 + "<code>start</code> being inclusive and <code>end</code> being exclusive.",
Damien Martin-Guillerez014388c2016-06-14 10:28:31 +0000873 parameters = {
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000874 @Param(name = "self", type = String.class, doc = "This string."),
Damien Martin-Guillerez014388c2016-06-14 10:28:31 +0000875 @Param(name = "sub", type = String.class, doc = "The substring to count."),
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000876 @Param(name = "start", type = Integer.class, defaultValue = "0",
877 doc = "Restrict to search from this position."),
878 @Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None",
879 doc = "optional position before which to restrict to search.")})
Laurent Le Brunb525bee2016-03-07 17:14:10 +0000880 private static final BuiltinFunction count = new BuiltinFunction("count") {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000881 public Integer invoke(String self, String sub, Integer start, Object end)
882 throws ConversionException {
883 String str = pythonSubstring(self, start, end, "'end' operand of 'find'");
884 if (sub.isEmpty()) {
885 return str.length() + 1;
886 }
887 int count = 0;
888 int index = -1;
889 while ((index = str.indexOf(sub)) >= 0) {
890 count++;
891 str = str.substring(index + sub.length());
892 }
893 return count;
894 }
895 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100896
laurentlb76972e22018-03-21 15:39:11 -0700897 @SkylarkSignature(
898 name = "elems",
899 objectType = StringModule.class,
900 returnType = SkylarkList.class,
901 doc =
902 "Returns an iterable value containing successive 1-element substrings of the string. "
903 + "Equivalent to <code>[s[i] for i in range(len(s))]</code>, except that the "
904 + "returned value might not be a list.",
905 parameters = {@Param(name = "self", type = String.class, doc = "This string.")}
906 )
907 private static final BuiltinFunction elems =
908 new BuiltinFunction("elems") {
909 public SkylarkList<String> invoke(String self) throws ConversionException {
910 ImmutableList.Builder<String> builder = new ImmutableList.Builder<>();
911 for (char c : self.toCharArray()) {
912 builder.add(String.valueOf(c));
913 }
914 return SkylarkList.createImmutable(builder.build());
915 }
916 };
917
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000918 @SkylarkSignature(name = "endswith", objectType = StringModule.class, returnType = Boolean.class,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100919 doc = "Returns True if the string ends with <code>sub</code>, "
920 + "otherwise False, optionally restricting to [<code>start</code>:<code>end</code>], "
921 + "<code>start</code> being inclusive and <code>end</code> being exclusive.",
Damien Martin-Guillerez014388c2016-06-14 10:28:31 +0000922 parameters = {
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000923 @Param(name = "self", type = String.class, doc = "This string."),
Damien Martin-Guillerez014388c2016-06-14 10:28:31 +0000924 @Param(name = "sub", type = String.class, doc = "The substring to check."),
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000925 @Param(name = "start", type = Integer.class, defaultValue = "0",
926 doc = "Test beginning at this position."),
927 @Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None",
928 doc = "optional position at which to stop comparing.")})
Laurent Le Brunb525bee2016-03-07 17:14:10 +0000929 private static final BuiltinFunction endswith = new BuiltinFunction("endswith") {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000930 public Boolean invoke(String self, String sub, Integer start, Object end)
931 throws ConversionException {
932 return pythonSubstring(self, start, end, "'end' operand of 'endswith'").endsWith(sub);
933 }
934 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100935
Laurent Le Brunaa83c3a2015-05-08 15:00:35 +0000936 // In Python, formatting is very complex.
937 // We handle here the simplest case which provides most of the value of the function.
938 // https://docs.python.org/3/library/string.html#formatstrings
Laurent Le Brune51a4d22016-10-11 18:04:16 +0000939 @SkylarkSignature(
940 name = "format",
941 objectType = StringModule.class,
942 returnType = String.class,
943 doc =
944 "Perform string interpolation. Format strings contain replacement fields "
945 + "surrounded by curly braces <code>{}</code>. Anything that is not contained "
946 + "in braces is considered literal text, which is copied unchanged to the output."
947 + "If you need to include a brace character in the literal text, it can be "
948 + "escaped by doubling: <code>{{</code> and <code>}}</code>"
949 + "A replacement field can be either a name, a number, or empty. Values are "
950 + "converted to strings using the <a href=\"globals.html#str\">str</a> function."
951 + "<pre class=\"language-python\">"
952 + "# Access in order:\n"
953 + "\"{} < {}\".format(4, 5) == \"4 < 5\"\n"
954 + "# Access by position:\n"
955 + "\"{1}, {0}\".format(2, 1) == \"1, 2\"\n"
956 + "# Access by name:\n"
957 + "\"x{key}x\".format(key = 2) == \"x2x\"</pre>\n",
958 parameters = {
959 @Param(name = "self", type = String.class, doc = "This string."),
960 },
961 extraPositionals =
962 @Param(
963 name = "args",
964 type = SkylarkList.class,
965 defaultValue = "()",
John Caterd7928422017-01-03 20:06:59 +0000966 doc = "List of arguments."
Laurent Le Brune51a4d22016-10-11 18:04:16 +0000967 ),
968 extraKeywords =
969 @Param(
970 name = "kwargs",
971 type = SkylarkDict.class,
972 defaultValue = "{}",
John Caterd7928422017-01-03 20:06:59 +0000973 doc = "Dictionary of arguments."
Laurent Le Brune51a4d22016-10-11 18:04:16 +0000974 ),
vladmoscd6d8ae2017-10-12 15:35:17 +0200975 useLocation = true
Laurent Le Brune51a4d22016-10-11 18:04:16 +0000976 )
977 private static final BuiltinFunction format =
978 new BuiltinFunction("format") {
979 @SuppressWarnings("unused")
980 public String invoke(
laurentlbc9b6f4a2017-06-21 11:58:50 +0200981 String self,
982 SkylarkList<Object> args,
983 SkylarkDict<?, ?> kwargs,
vladmoscd6d8ae2017-10-12 15:35:17 +0200984 Location loc)
Laurent Le Brune51a4d22016-10-11 18:04:16 +0000985 throws EvalException {
vladmoscd6d8ae2017-10-12 15:35:17 +0200986 return new FormatParser(loc)
Laurent Le Brune51a4d22016-10-11 18:04:16 +0000987 .format(
988 self,
989 args.getImmutableList(),
990 kwargs.getContents(String.class, Object.class, "kwargs"));
991 }
992 };
Laurent Le Brunaa83c3a2015-05-08 15:00:35 +0000993
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000994 @SkylarkSignature(name = "startswith", objectType = StringModule.class,
995 returnType = Boolean.class,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100996 doc = "Returns True if the string starts with <code>sub</code>, "
997 + "otherwise False, optionally restricting to [<code>start</code>:<code>end</code>], "
998 + "<code>start</code> being inclusive and <code>end</code> being exclusive.",
Damien Martin-Guillerez014388c2016-06-14 10:28:31 +0000999 parameters = {
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001000 @Param(name = "self", type = String.class, doc = "This string."),
Damien Martin-Guillerez014388c2016-06-14 10:28:31 +00001001 @Param(name = "sub", type = String.class, doc = "The substring to check."),
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001002 @Param(name = "start", type = Integer.class, defaultValue = "0",
1003 doc = "Test beginning at this position."),
1004 @Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None",
1005 doc = "Stop comparing at this position.")})
Laurent Le Brunb525bee2016-03-07 17:14:10 +00001006 private static final BuiltinFunction startswith = new BuiltinFunction("startswith") {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00001007 public Boolean invoke(String self, String sub, Integer start, Object end)
1008 throws ConversionException {
1009 return pythonSubstring(self, start, end, "'end' operand of 'startswith'").startsWith(sub);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001010 }
1011 };
1012
Florian Weikert5e8752b2015-12-11 21:54:43 +00001013 @SkylarkSignature(
1014 name = "min",
1015 returnType = Object.class,
1016 doc =
1017 "Returns the smallest one of all given arguments. "
laurentlb10591042017-07-20 18:16:18 +02001018 + "If only one argument is provided, it must be a non-empty iterable. "
1019 + "It is an error if elements are not comparable (for example int with string). "
1020 + "<pre class=\"language-python\">min(2, 5, 4) == 2\n"
1021 + "min([5, 6, 3]) == 3</pre>",
Damien Martin-Guillereze3108c52016-06-08 08:54:45 +00001022 extraPositionals =
Vladimir Moskva7f0cd622017-02-16 13:48:37 +00001023 @Param(name = "args", type = SkylarkList.class, doc = "The elements to be checked."),
laurentlbc9b6f4a2017-06-21 11:58:50 +02001024 useLocation = true,
1025 useEnvironment = true
Florian Weikert5e8752b2015-12-11 21:54:43 +00001026 )
Vladimir Moskva7f0cd622017-02-16 13:48:37 +00001027 private static final BuiltinFunction min =
1028 new BuiltinFunction("min") {
1029 @SuppressWarnings("unused") // Accessed via Reflection.
laurentlbc9b6f4a2017-06-21 11:58:50 +02001030 public Object invoke(SkylarkList<?> args, Location loc, Environment env)
1031 throws EvalException {
Vladimir Moskva7f0cd622017-02-16 13:48:37 +00001032 try {
laurentlbc9b6f4a2017-06-21 11:58:50 +02001033 return findExtreme(args, EvalUtils.SKYLARK_COMPARATOR.reverse(), loc, env);
Vladimir Moskva7f0cd622017-02-16 13:48:37 +00001034 } catch (ComparisonException e) {
1035 throw new EvalException(loc, e);
1036 }
1037 }
1038 };
Florian Weikert5e8752b2015-12-11 21:54:43 +00001039
1040 @SkylarkSignature(
1041 name = "max",
1042 returnType = Object.class,
1043 doc =
1044 "Returns the largest one of all given arguments. "
laurentlb10591042017-07-20 18:16:18 +02001045 + "If only one argument is provided, it must be a non-empty iterable."
1046 + "It is an error if elements are not comparable (for example int with string). "
1047 + "<pre class=\"language-python\">max(2, 5, 4) == 5\n"
1048 + "max([5, 6, 3]) == 6</pre>",
Damien Martin-Guillereze3108c52016-06-08 08:54:45 +00001049 extraPositionals =
Vladimir Moskva7f0cd622017-02-16 13:48:37 +00001050 @Param(name = "args", type = SkylarkList.class, doc = "The elements to be checked."),
laurentlbc9b6f4a2017-06-21 11:58:50 +02001051 useLocation = true,
1052 useEnvironment = true
Florian Weikert5e8752b2015-12-11 21:54:43 +00001053 )
Vladimir Moskva7f0cd622017-02-16 13:48:37 +00001054 private static final BuiltinFunction max =
1055 new BuiltinFunction("max") {
1056 @SuppressWarnings("unused") // Accessed via Reflection.
laurentlbc9b6f4a2017-06-21 11:58:50 +02001057 public Object invoke(SkylarkList<?> args, Location loc, Environment env)
1058 throws EvalException {
Vladimir Moskva7f0cd622017-02-16 13:48:37 +00001059 try {
laurentlbc9b6f4a2017-06-21 11:58:50 +02001060 return findExtreme(args, EvalUtils.SKYLARK_COMPARATOR, loc, env);
Vladimir Moskva7f0cd622017-02-16 13:48:37 +00001061 } catch (ComparisonException e) {
1062 throw new EvalException(loc, e);
1063 }
1064 }
1065 };
Florian Weikert5e8752b2015-12-11 21:54:43 +00001066
laurentlbc9b6f4a2017-06-21 11:58:50 +02001067 /** Returns the maximum element from this list, as determined by maxOrdering. */
1068 private static Object findExtreme(
1069 SkylarkList<?> args, Ordering<Object> maxOrdering, Location loc, Environment env)
Florian Weikert5e8752b2015-12-11 21:54:43 +00001070 throws EvalException {
Jon Brandvein3cfeeec2017-01-20 04:23:37 +00001071 // Args can either be a list of items to compare, or a singleton list whose element is an
1072 // iterable of items to compare. In either case, there must be at least one item to compare.
Florian Weikert5e8752b2015-12-11 21:54:43 +00001073 try {
laurentlbc9b6f4a2017-06-21 11:58:50 +02001074 Iterable<?> items = (args.size() == 1) ? EvalUtils.toIterable(args.get(0), loc, env) : args;
Jon Brandvein3cfeeec2017-01-20 04:23:37 +00001075 return maxOrdering.max(items);
Florian Weikert5e8752b2015-12-11 21:54:43 +00001076 } catch (NoSuchElementException ex) {
Jon Brandvein3cfeeec2017-01-20 04:23:37 +00001077 throw new EvalException(loc, "expected at least one item");
Florian Weikert5e8752b2015-12-11 21:54:43 +00001078 }
1079 }
1080
Florian Weikert233a46e2015-12-16 12:38:38 +00001081 @SkylarkSignature(
1082 name = "all",
1083 returnType = Boolean.class,
laurentlb10591042017-07-20 18:16:18 +02001084 doc =
1085 "Returns true if all elements evaluate to True or if the collection is empty. "
mpnaa0ca5b2017-09-15 01:47:53 +02001086 + "Elements are converted to boolean using the <a href=\"#bool\">bool</a> function."
laurentlb10591042017-07-20 18:16:18 +02001087 + "<pre class=\"language-python\">all([\"hello\", 3, True]) == True\n"
1088 + "all([-1, 0, 1]) == False</pre>",
Damien Martin-Guillerez014388c2016-06-14 10:28:31 +00001089 parameters = {
Florian Weikert233a46e2015-12-16 12:38:38 +00001090 @Param(name = "elements", type = Object.class, doc = "A string or a collection of elements.")
1091 },
laurentlbc9b6f4a2017-06-21 11:58:50 +02001092 useLocation = true,
1093 useEnvironment = true
Florian Weikert233a46e2015-12-16 12:38:38 +00001094 )
Laurent Le Brunb525bee2016-03-07 17:14:10 +00001095 private static final BuiltinFunction all =
Florian Weikert233a46e2015-12-16 12:38:38 +00001096 new BuiltinFunction("all") {
1097 @SuppressWarnings("unused") // Accessed via Reflection.
laurentlbc9b6f4a2017-06-21 11:58:50 +02001098 public Boolean invoke(Object collection, Location loc, Environment env)
1099 throws EvalException {
1100 return !hasElementWithBooleanValue(collection, false, loc, env);
Florian Weikert233a46e2015-12-16 12:38:38 +00001101 }
1102 };
1103
1104 @SkylarkSignature(
1105 name = "any",
1106 returnType = Boolean.class,
laurentlb10591042017-07-20 18:16:18 +02001107 doc =
mpnaa0ca5b2017-09-15 01:47:53 +02001108 "Returns true if at least one element evaluates to True. "
1109 + "Elements are converted to boolean using the <a href=\"#bool\">bool</a> function."
laurentlb10591042017-07-20 18:16:18 +02001110 + "<pre class=\"language-python\">any([-1, 0, 1]) == True\n"
1111 + "any([False, 0, \"\"]) == False</pre>",
Damien Martin-Guillerez014388c2016-06-14 10:28:31 +00001112 parameters = {
Florian Weikert233a46e2015-12-16 12:38:38 +00001113 @Param(name = "elements", type = Object.class, doc = "A string or a collection of elements.")
1114 },
laurentlbc9b6f4a2017-06-21 11:58:50 +02001115 useLocation = true,
1116 useEnvironment = true
Florian Weikert233a46e2015-12-16 12:38:38 +00001117 )
Laurent Le Brunb525bee2016-03-07 17:14:10 +00001118 private static final BuiltinFunction any =
Florian Weikert233a46e2015-12-16 12:38:38 +00001119 new BuiltinFunction("any") {
1120 @SuppressWarnings("unused") // Accessed via Reflection.
laurentlbc9b6f4a2017-06-21 11:58:50 +02001121 public Boolean invoke(Object collection, Location loc, Environment env)
1122 throws EvalException {
1123 return hasElementWithBooleanValue(collection, true, loc, env);
Florian Weikert233a46e2015-12-16 12:38:38 +00001124 }
1125 };
1126
laurentlbc9b6f4a2017-06-21 11:58:50 +02001127 private static boolean hasElementWithBooleanValue(
1128 Object collection, boolean value, Location loc, Environment env) throws EvalException {
1129 Iterable<?> iterable = EvalUtils.toIterable(collection, loc, env);
Florian Weikert233a46e2015-12-16 12:38:38 +00001130 for (Object obj : iterable) {
1131 if (EvalUtils.toBoolean(obj) == value) {
1132 return true;
1133 }
1134 }
1135 return false;
1136 }
1137
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001138 // supported list methods
Laurent Le Brun6a4d36a2015-08-21 10:57:41 +00001139 @SkylarkSignature(
1140 name = "sorted",
Francois-Rene Rideau93ed7f12015-10-20 15:39:33 +00001141 returnType = MutableList.class,
Laurent Le Brun6a4d36a2015-08-21 10:57:41 +00001142 doc =
Vladimir Moskva7f0cd622017-02-16 13:48:37 +00001143 "Sort a collection. Elements should all belong to the same orderable type, they are sorted "
laurentlb10591042017-07-20 18:16:18 +02001144 + "by their value (in ascending order). "
1145 + "It is an error if elements are not comparable (for example int with string)."
1146 + "<pre class=\"language-python\">sorted([3, 5, 4]) == [3, 4, 5]</pre>",
Damien Martin-Guillerez014388c2016-06-14 10:28:31 +00001147 parameters = {@Param(name = "self", type = Object.class, doc = "This collection.")},
Laurent Le Brun6a4d36a2015-08-21 10:57:41 +00001148 useLocation = true,
1149 useEnvironment = true
1150 )
Laurent Le Brunb525bee2016-03-07 17:14:10 +00001151 private static final BuiltinFunction sorted =
Laurent Le Brun6a4d36a2015-08-21 10:57:41 +00001152 new BuiltinFunction("sorted") {
brandjonc06e7462017-07-11 20:54:58 +02001153 public MutableList<?> invoke(Object self, Location loc, Environment env)
Laurent Le Brune51a4d22016-10-11 18:04:16 +00001154 throws EvalException {
Laurent Le Brun6a4d36a2015-08-21 10:57:41 +00001155 try {
michajloff50f282017-10-05 20:02:51 +02001156 return MutableList.copyOf(
1157 env,
1158 EvalUtils.SKYLARK_COMPARATOR.sortedCopy(EvalUtils.toCollection(self, loc, env)));
Laurent Le Brun6a4d36a2015-08-21 10:57:41 +00001159 } catch (EvalUtils.ComparisonException e) {
1160 throw new EvalException(loc, e);
1161 }
1162 }
1163 };
Laurent Le Brunef69ec52015-04-16 18:58:34 +00001164
Francois-Rene Rideau4e994102015-09-17 22:41:28 +00001165 @SkylarkSignature(
Florian Weikertd5e33502015-12-14 12:06:10 +00001166 name = "reversed",
1167 returnType = MutableList.class,
laurentlb10591042017-07-20 18:16:18 +02001168 doc =
1169 "Returns a list that contains the elements of the original sequence in reversed order."
1170 + "<pre class=\"language-python\">reversed([3, 5, 4]) == [4, 5, 3]</pre>",
Damien Martin-Guillerez014388c2016-06-14 10:28:31 +00001171 parameters = {
Florian Weikertd5e33502015-12-14 12:06:10 +00001172 @Param(
1173 name = "sequence",
1174 type = Object.class,
1175 doc = "The sequence to be reversed (string, list or tuple)."
1176 )
1177 },
1178 useLocation = true,
1179 useEnvironment = true
1180 )
Laurent Le Brunb525bee2016-03-07 17:14:10 +00001181 private static final BuiltinFunction reversed =
Florian Weikertd5e33502015-12-14 12:06:10 +00001182 new BuiltinFunction("reversed") {
1183 @SuppressWarnings("unused") // Accessed via Reflection.
Francois-Rene Rideauab049e02016-02-17 16:13:46 +00001184 public MutableList<?> invoke(Object sequence, Location loc, Environment env)
Florian Weikertd5e33502015-12-14 12:06:10 +00001185 throws EvalException {
1186 // We only allow lists and strings.
Francois-Rene Rideauab049e02016-02-17 16:13:46 +00001187 if (sequence instanceof SkylarkDict) {
Florian Weikertd5e33502015-12-14 12:06:10 +00001188 throw new EvalException(
1189 loc, "Argument to reversed() must be a sequence, not a dictionary.");
1190 } else if (sequence instanceof NestedSet || sequence instanceof SkylarkNestedSet) {
Vladimir Moskvad200daf2016-12-23 16:35:37 +00001191 throw new EvalException(
1192 loc, "Argument to reversed() must be a sequence, not a depset.");
Florian Weikertd5e33502015-12-14 12:06:10 +00001193 }
brandjondc2c5502017-12-07 14:30:04 -08001194 ArrayDeque<Object> tmpList = new ArrayDeque<>();
laurentlbc9b6f4a2017-06-21 11:58:50 +02001195 for (Object element : EvalUtils.toIterable(sequence, loc, env)) {
Florian Weikertd5e33502015-12-14 12:06:10 +00001196 tmpList.addFirst(element);
1197 }
michajloff50f282017-10-05 20:02:51 +02001198 return MutableList.copyOf(env, tmpList);
Florian Weikertd5e33502015-12-14 12:06:10 +00001199 }
1200 };
1201
1202 @SkylarkSignature(
Francois-Rene Rideau4e994102015-09-17 22:41:28 +00001203 name = "append",
1204 objectType = MutableList.class,
1205 returnType = Runtime.NoneType.class,
Francois-Rene Rideau4e994102015-09-17 22:41:28 +00001206 doc = "Adds an item to the end of the list.",
Damien Martin-Guillerez014388c2016-06-14 10:28:31 +00001207 parameters = {
Francois-Rene Rideau4e994102015-09-17 22:41:28 +00001208 @Param(name = "self", type = MutableList.class, doc = "This list."),
1209 @Param(name = "item", type = Object.class, doc = "Item to add at the end.")
1210 },
1211 useLocation = true,
Laurent Le Brune51a4d22016-10-11 18:04:16 +00001212 useEnvironment = true
1213 )
Laurent Le Brunb525bee2016-03-07 17:14:10 +00001214 private static final BuiltinFunction append =
Francois-Rene Rideau4e994102015-09-17 22:41:28 +00001215 new BuiltinFunction("append") {
Laurent Le Brune51a4d22016-10-11 18:04:16 +00001216 public Runtime.NoneType invoke(
1217 MutableList<Object> self, Object item, Location loc, Environment env)
1218 throws EvalException {
brandjon0528d5d2017-08-04 16:00:56 +02001219 self.add(item, loc, env.mutability());
Francois-Rene Rideau4e994102015-09-17 22:41:28 +00001220 return Runtime.NONE;
1221 }
1222 };
1223
Francois-Rene Rideau4e994102015-09-17 22:41:28 +00001224 @SkylarkSignature(
Yue Gan6c2276a2016-04-07 08:02:00 +00001225 name = "insert",
1226 objectType = MutableList.class,
1227 returnType = Runtime.NoneType.class,
1228 doc = "Inserts an item at a given position.",
Damien Martin-Guillerez014388c2016-06-14 10:28:31 +00001229 parameters = {
Yue Gan6c2276a2016-04-07 08:02:00 +00001230 @Param(name = "self", type = MutableList.class, doc = "This list."),
1231 @Param(name = "index", type = Integer.class, doc = "The index of the given position."),
1232 @Param(name = "item", type = Object.class, doc = "The item.")
1233 },
1234 useLocation = true,
1235 useEnvironment = true
1236 )
1237 private static final BuiltinFunction insert =
1238 new BuiltinFunction("insert") {
1239 public Runtime.NoneType invoke(
1240 MutableList<Object> self, Integer index, Object item, Location loc, Environment env)
Laurent Le Brune51a4d22016-10-11 18:04:16 +00001241 throws EvalException {
brandjon0528d5d2017-08-04 16:00:56 +02001242 self.add(EvalUtils.clampRangeEndpoint(index, self.size()), item, loc, env.mutability());
Yue Gan6c2276a2016-04-07 08:02:00 +00001243 return Runtime.NONE;
1244 }
1245 };
1246
1247 @SkylarkSignature(
Francois-Rene Rideau4e994102015-09-17 22:41:28 +00001248 name = "extend",
1249 objectType = MutableList.class,
1250 returnType = Runtime.NoneType.class,
Francois-Rene Rideau4e994102015-09-17 22:41:28 +00001251 doc = "Adds all items to the end of the list.",
Damien Martin-Guillerez014388c2016-06-14 10:28:31 +00001252 parameters = {
Francois-Rene Rideau4e994102015-09-17 22:41:28 +00001253 @Param(name = "self", type = MutableList.class, doc = "This list."),
Yue Gan6c2276a2016-04-07 08:02:00 +00001254 @Param(name = "items", type = SkylarkList.class, doc = "Items to add at the end.")
1255 },
Francois-Rene Rideau4e994102015-09-17 22:41:28 +00001256 useLocation = true,
Yue Gan6c2276a2016-04-07 08:02:00 +00001257 useEnvironment = true
1258 )
Laurent Le Brunb525bee2016-03-07 17:14:10 +00001259 private static final BuiltinFunction extend =
Francois-Rene Rideau4e994102015-09-17 22:41:28 +00001260 new BuiltinFunction("extend") {
Yue Gan6c2276a2016-04-07 08:02:00 +00001261 public Runtime.NoneType invoke(
1262 MutableList<Object> self, SkylarkList<Object> items, Location loc, Environment env)
Laurent Le Brune51a4d22016-10-11 18:04:16 +00001263 throws EvalException {
brandjon0528d5d2017-08-04 16:00:56 +02001264 self.addAll(items, loc, env.mutability());
Francois-Rene Rideau4e994102015-09-17 22:41:28 +00001265 return Runtime.NONE;
1266 }
1267 };
1268
Laurent Le Brun3ef1eea2015-11-09 14:35:54 +00001269 @SkylarkSignature(
1270 name = "index",
1271 objectType = MutableList.class,
1272 returnType = Integer.class,
1273 doc =
1274 "Returns the index in the list of the first item whose value is x. "
1275 + "It is an error if there is no such item.",
Damien Martin-Guillerez014388c2016-06-14 10:28:31 +00001276 parameters = {
Laurent Le Brun8853df92015-12-16 15:02:03 +00001277 @Param(name = "self", type = MutableList.class, doc = "This list."),
Laurent Le Brun3ef1eea2015-11-09 14:35:54 +00001278 @Param(name = "x", type = Object.class, doc = "The object to search.")
1279 },
1280 useLocation = true
1281 )
Laurent Le Brunb525bee2016-03-07 17:14:10 +00001282 private static final BuiltinFunction listIndex =
Laurent Le Brun3ef1eea2015-11-09 14:35:54 +00001283 new BuiltinFunction("index") {
Francois-Rene Rideauab049e02016-02-17 16:13:46 +00001284 public Integer invoke(MutableList<?> self, Object x, Location loc) throws EvalException {
Laurent Le Brun3ef1eea2015-11-09 14:35:54 +00001285 int i = 0;
1286 for (Object obj : self) {
1287 if (obj.equals(x)) {
1288 return i;
1289 }
1290 i++;
1291 }
Laurent Le Brunc31f3512016-12-29 21:41:33 +00001292 throw new EvalException(loc, Printer.format("item %r not found in list", x));
Laurent Le Brun3ef1eea2015-11-09 14:35:54 +00001293 }
1294 };
1295
Laurent Le Brun8853df92015-12-16 15:02:03 +00001296 @SkylarkSignature(
1297 name = "remove",
1298 objectType = MutableList.class,
1299 returnType = Runtime.NoneType.class,
1300 doc =
1301 "Removes the first item from the list whose value is x. "
1302 + "It is an error if there is no such item.",
Damien Martin-Guillerez014388c2016-06-14 10:28:31 +00001303 parameters = {
Laurent Le Brun8853df92015-12-16 15:02:03 +00001304 @Param(name = "self", type = MutableList.class, doc = "This list."),
1305 @Param(name = "x", type = Object.class, doc = "The object to remove.")
1306 },
1307 useLocation = true,
1308 useEnvironment = true
1309 )
Laurent Le Brunb525bee2016-03-07 17:14:10 +00001310 private static final BuiltinFunction listRemove =
Laurent Le Brun8853df92015-12-16 15:02:03 +00001311 new BuiltinFunction("remove") {
Francois-Rene Rideauab049e02016-02-17 16:13:46 +00001312 public Runtime.NoneType invoke(MutableList<?> self, Object x, Location loc, Environment env)
Laurent Le Brun8853df92015-12-16 15:02:03 +00001313 throws EvalException {
1314 for (int i = 0; i < self.size(); i++) {
1315 if (self.get(i).equals(x)) {
brandjon0528d5d2017-08-04 16:00:56 +02001316 self.remove(i, loc, env.mutability());
Laurent Le Brun8853df92015-12-16 15:02:03 +00001317 return Runtime.NONE;
1318 }
1319 }
Laurent Le Brunc31f3512016-12-29 21:41:33 +00001320 throw new EvalException(loc, Printer.format("item %r not found in list", x));
Laurent Le Brun8853df92015-12-16 15:02:03 +00001321 }
1322 };
1323
Laurent Le Brun3a837472015-12-22 17:58:40 +00001324 @SkylarkSignature(
1325 name = "pop",
1326 objectType = MutableList.class,
1327 returnType = Object.class,
1328 doc =
1329 "Removes the item at the given position in the list, and returns it. "
Francois-Rene Rideau432d7152016-02-18 16:33:03 +00001330 + "If no <code>index</code> is specified, "
1331 + "it removes and returns the last item in the list.",
Damien Martin-Guillerez014388c2016-06-14 10:28:31 +00001332 parameters = {
Laurent Le Brun3a837472015-12-22 17:58:40 +00001333 @Param(name = "self", type = MutableList.class, doc = "This list."),
Laurent Le Brun3a837472015-12-22 17:58:40 +00001334 @Param(
1335 name = "i",
1336 type = Integer.class,
1337 noneable = true,
1338 defaultValue = "None",
1339 doc = "The index of the item."
1340 )
1341 },
1342 useLocation = true,
1343 useEnvironment = true
1344 )
Laurent Le Brunb525bee2016-03-07 17:14:10 +00001345 private static final BuiltinFunction listPop =
Laurent Le Brun3a837472015-12-22 17:58:40 +00001346 new BuiltinFunction("pop") {
Francois-Rene Rideauab049e02016-02-17 16:13:46 +00001347 public Object invoke(MutableList<?> self, Object i, Location loc, Environment env)
Laurent Le Brun3a837472015-12-22 17:58:40 +00001348 throws EvalException {
1349 int arg = i == Runtime.NONE ? -1 : (Integer) i;
Jon Brandveinfab84872016-11-11 16:27:01 +00001350 int index = EvalUtils.getSequenceIndex(arg, self.size(), loc);
Laurent Le Brun3a837472015-12-22 17:58:40 +00001351 Object result = self.get(index);
brandjon0528d5d2017-08-04 16:00:56 +02001352 self.remove(index, loc, env.mutability());
Laurent Le Brun3a837472015-12-22 17:58:40 +00001353 return result;
1354 }
1355 };
1356
Francois-Rene Rideau432d7152016-02-18 16:33:03 +00001357 @SkylarkSignature(
1358 name = "pop",
1359 objectType = SkylarkDict.class,
1360 returnType = Object.class,
1361 doc =
1362 "Removes a <code>key</code> from the dict, and returns the associated value. "
1363 + "If entry with that key was found, return the specified <code>default</code> value;"
1364 + "if no default value was specified, fail instead.",
Damien Martin-Guillerez014388c2016-06-14 10:28:31 +00001365 parameters = {
Francois-Rene Rideau432d7152016-02-18 16:33:03 +00001366 @Param(name = "self", type = SkylarkDict.class, doc = "This dict."),
1367 @Param(name = "key", type = Object.class, doc = "The key."),
Francois-Rene Rideau432d7152016-02-18 16:33:03 +00001368 @Param(name = "default", type = Object.class, defaultValue = "unbound",
1369 doc = "a default value if the key is absent."),
1370 },
1371 useLocation = true,
1372 useEnvironment = true
1373 )
Laurent Le Brunb525bee2016-03-07 17:14:10 +00001374 private static final BuiltinFunction dictPop =
Francois-Rene Rideau432d7152016-02-18 16:33:03 +00001375 new BuiltinFunction("pop") {
1376 public Object invoke(SkylarkDict<Object, Object> self, Object key, Object defaultValue,
1377 Location loc, Environment env)
1378 throws EvalException {
1379 Object value = self.get(key);
1380 if (value != null) {
brandjon9e654942017-08-09 23:45:50 +02001381 self.remove(key, loc, env.mutability());
Francois-Rene Rideau432d7152016-02-18 16:33:03 +00001382 return value;
1383 }
1384 if (defaultValue != Runtime.UNBOUND) {
1385 return defaultValue;
1386 }
1387 throw new EvalException(loc, Printer.format("KeyError: %r", key));
1388 }
1389 };
1390
1391 @SkylarkSignature(
1392 name = "popitem",
1393 objectType = SkylarkDict.class,
1394 returnType = Tuple.class,
1395 doc =
1396 "Remove and return an arbitrary <code>(key, value)</code> pair from the dictionary. "
1397 + "<code>popitem()</code> is useful to destructively iterate over a dictionary, "
1398 + "as often used in set algorithms. "
1399 + "If the dictionary is empty, calling <code>popitem()</code> fails. "
Vladimir Moskva76e31d12016-12-05 16:28:37 +00001400 + "It is deterministic which pair is returned.",
Damien Martin-Guillerez014388c2016-06-14 10:28:31 +00001401 parameters = {
Francois-Rene Rideau432d7152016-02-18 16:33:03 +00001402 @Param(name = "self", type = SkylarkDict.class, doc = "This dict.")
1403 },
1404 useLocation = true,
1405 useEnvironment = true
1406 )
Laurent Le Brunb525bee2016-03-07 17:14:10 +00001407 private static final BuiltinFunction dictPopItem =
Francois-Rene Rideau432d7152016-02-18 16:33:03 +00001408 new BuiltinFunction("popitem") {
1409 public Tuple<Object> invoke(SkylarkDict<Object, Object> self,
1410 Location loc, Environment env)
1411 throws EvalException {
1412 if (self.isEmpty()) {
1413 throw new EvalException(loc, "popitem(): dictionary is empty");
1414 }
brandjon9e654942017-08-09 23:45:50 +02001415 Object key = self.keySet().iterator().next();
Francois-Rene Rideau432d7152016-02-18 16:33:03 +00001416 Object value = self.get(key);
brandjon9e654942017-08-09 23:45:50 +02001417 self.remove(key, loc, env.mutability());
brandjonc06e7462017-07-11 20:54:58 +02001418 return Tuple.of(key, value);
Francois-Rene Rideau432d7152016-02-18 16:33:03 +00001419 }
1420 };
1421
1422 @SkylarkSignature(
1423 name = "clear",
1424 objectType = SkylarkDict.class,
1425 returnType = Runtime.NoneType.class,
1426 doc = "Remove all items from the dictionary.",
Damien Martin-Guillerez014388c2016-06-14 10:28:31 +00001427 parameters = {
Francois-Rene Rideau432d7152016-02-18 16:33:03 +00001428 @Param(name = "self", type = SkylarkDict.class, doc = "This dict.")
1429 },
1430 useLocation = true,
1431 useEnvironment = true
1432 )
Laurent Le Brunb525bee2016-03-07 17:14:10 +00001433 private static final BuiltinFunction dictClear =
Francois-Rene Rideau432d7152016-02-18 16:33:03 +00001434 new BuiltinFunction("clear") {
1435 public Runtime.NoneType invoke(SkylarkDict<Object, Object> self,
1436 Location loc, Environment env)
1437 throws EvalException {
brandjon9e654942017-08-09 23:45:50 +02001438 self.clear(loc, env.mutability());
Francois-Rene Rideau432d7152016-02-18 16:33:03 +00001439 return Runtime.NONE;
1440 }
1441 };
1442
1443 @SkylarkSignature(
1444 name = "setdefault",
1445 objectType = SkylarkDict.class,
1446 returnType = Object.class,
1447 doc =
1448 "If <code>key</code> is in the dictionary, return its value. "
1449 + "If not, insert key with a value of <code>default</code> "
1450 + "and return <code>default</code>. "
1451 + "<code>default</code> defaults to <code>None</code>.",
Damien Martin-Guillerez014388c2016-06-14 10:28:31 +00001452 parameters = {
Francois-Rene Rideau432d7152016-02-18 16:33:03 +00001453 @Param(name = "self", type = SkylarkDict.class, doc = "This dict."),
1454 @Param(name = "key", type = Object.class, doc = "The key."),
Laurent Le Brunb525bee2016-03-07 17:14:10 +00001455 @Param(
1456 name = "default",
1457 type = Object.class,
1458 defaultValue = "None",
1459 doc = "a default value if the key is absent."
1460 ),
Francois-Rene Rideau432d7152016-02-18 16:33:03 +00001461 },
1462 useLocation = true,
1463 useEnvironment = true
1464 )
Laurent Le Brunb525bee2016-03-07 17:14:10 +00001465 private static final BuiltinFunction dictSetDefault =
Francois-Rene Rideau432d7152016-02-18 16:33:03 +00001466 new BuiltinFunction("setdefault") {
Laurent Le Brunb525bee2016-03-07 17:14:10 +00001467 public Object invoke(
1468 SkylarkDict<Object, Object> self,
1469 Object key,
1470 Object defaultValue,
1471 Location loc,
1472 Environment env)
Francois-Rene Rideau432d7152016-02-18 16:33:03 +00001473 throws EvalException {
1474 Object value = self.get(key);
1475 if (value != null) {
1476 return value;
1477 }
1478 self.put(key, defaultValue, loc, env);
1479 return defaultValue;
1480 }
1481 };
1482
Laurent Le Brunb525bee2016-03-07 17:14:10 +00001483 @SkylarkSignature(
1484 name = "update",
1485 objectType = SkylarkDict.class,
1486 returnType = Runtime.NoneType.class,
1487 doc = "Update the dictionary with the key/value pairs from other, overwriting existing keys.",
Damien Martin-Guillerez014388c2016-06-14 10:28:31 +00001488 parameters = {
Laurent Le Brunb525bee2016-03-07 17:14:10 +00001489 @Param(name = "self", type = SkylarkDict.class, doc = "This dict."),
1490 @Param(name = "other", type = SkylarkDict.class, doc = "The values to add."),
1491 },
1492 useLocation = true,
1493 useEnvironment = true
1494 )
1495 private static final BuiltinFunction dictUpdate =
1496 new BuiltinFunction("update") {
1497 public Runtime.NoneType invoke(
1498 SkylarkDict<Object, Object> self,
1499 SkylarkDict<Object, Object> other,
1500 Location loc,
1501 Environment env)
1502 throws EvalException {
brandjon9e654942017-08-09 23:45:50 +02001503 self.putAll(other, loc, env.mutability());
Laurent Le Brunb525bee2016-03-07 17:14:10 +00001504 return Runtime.NONE;
1505 }
1506 };
1507
Laurent Le Brune51a4d22016-10-11 18:04:16 +00001508 @SkylarkSignature(
1509 name = "values",
1510 objectType = SkylarkDict.class,
1511 returnType = MutableList.class,
1512 doc =
Vladimir Moskva76e31d12016-12-05 16:28:37 +00001513 "Returns the list of values:"
Laurent Le Brune51a4d22016-10-11 18:04:16 +00001514 + "<pre class=\"language-python\">"
Vladimir Moskva76e31d12016-12-05 16:28:37 +00001515 + "{2: \"a\", 4: \"b\", 1: \"c\"}.values() == [\"a\", \"b\", \"c\"]</pre>\n",
Laurent Le Brune51a4d22016-10-11 18:04:16 +00001516 parameters = {@Param(name = "self", type = SkylarkDict.class, doc = "This dict.")},
1517 useEnvironment = true
1518 )
1519 private static final BuiltinFunction values =
1520 new BuiltinFunction("values") {
1521 public MutableList<?> invoke(SkylarkDict<?, ?> self, Environment env) throws EvalException {
michajloff50f282017-10-05 20:02:51 +02001522 return MutableList.copyOf(env, self.values());
Laurent Le Brune51a4d22016-10-11 18:04:16 +00001523 }
1524 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001525
Laurent Le Brune51a4d22016-10-11 18:04:16 +00001526 @SkylarkSignature(
1527 name = "items",
1528 objectType = SkylarkDict.class,
1529 returnType = MutableList.class,
1530 doc =
Vladimir Moskva76e31d12016-12-05 16:28:37 +00001531 "Returns the list of key-value tuples:"
Laurent Le Brune51a4d22016-10-11 18:04:16 +00001532 + "<pre class=\"language-python\">"
Vladimir Moskva76e31d12016-12-05 16:28:37 +00001533 + "{2: \"a\", 4: \"b\", 1: \"c\"}.items() == [(2, \"a\"), (4, \"b\"), (1, \"c\")]"
Laurent Le Brune51a4d22016-10-11 18:04:16 +00001534 + "</pre>\n",
1535 parameters = {@Param(name = "self", type = SkylarkDict.class, doc = "This dict.")},
1536 useEnvironment = true
1537 )
1538 private static final BuiltinFunction items =
1539 new BuiltinFunction("items") {
1540 public MutableList<?> invoke(SkylarkDict<?, ?> self, Environment env) throws EvalException {
michajlo490eb972017-10-16 21:30:13 +02001541 ArrayList<Object> list = Lists.newArrayListWithCapacity(self.size());
Laurent Le Brune51a4d22016-10-11 18:04:16 +00001542 for (Map.Entry<?, ?> entries : self.entrySet()) {
1543 list.add(Tuple.of(entries.getKey(), entries.getValue()));
1544 }
michajlo490eb972017-10-16 21:30:13 +02001545 return MutableList.wrapUnsafe(env, list);
Laurent Le Brune51a4d22016-10-11 18:04:16 +00001546 }
1547 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001548
Francois-Rene Rideauab049e02016-02-17 16:13:46 +00001549 @SkylarkSignature(name = "keys", objectType = SkylarkDict.class,
Francois-Rene Rideau93ed7f12015-10-20 15:39:33 +00001550 returnType = MutableList.class,
Vladimir Moskva76e31d12016-12-05 16:28:37 +00001551 doc = "Returns the list of keys:"
1552 + "<pre class=\"language-python\">{2: \"a\", 4: \"b\", 1: \"c\"}.keys() == [2, 4, 1]"
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +00001553 + "</pre>\n",
Damien Martin-Guillerez014388c2016-06-14 10:28:31 +00001554 parameters = {
Francois-Rene Rideauab049e02016-02-17 16:13:46 +00001555 @Param(name = "self", type = SkylarkDict.class, doc = "This dict.")},
Francois-Rene Rideau93ed7f12015-10-20 15:39:33 +00001556 useEnvironment = true)
Laurent Le Brunb525bee2016-03-07 17:14:10 +00001557 private static final BuiltinFunction keys = new BuiltinFunction("keys") {
Francois-Rene Rideauab049e02016-02-17 16:13:46 +00001558 // Skylark will only call this on a dict; and
1559 // allowed keys are all Comparable... if not mutually, it's OK to get a runtime exception.
1560 @SuppressWarnings("unchecked")
1561 public MutableList<?> invoke(SkylarkDict<?, ?> self,
Francois-Rene Rideau93ed7f12015-10-20 15:39:33 +00001562 Environment env) throws EvalException {
michajlo490eb972017-10-16 21:30:13 +02001563 ArrayList<Object> list = Lists.newArrayListWithCapacity(self.size());
Vladimir Moskva76e31d12016-12-05 16:28:37 +00001564 for (Map.Entry<?, ?> entries : self.entrySet()) {
1565 list.add(entries.getKey());
1566 }
michajlo490eb972017-10-16 21:30:13 +02001567 return MutableList.wrapUnsafe(env, list);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001568 }
1569 };
1570
Googlerde689132016-12-12 18:15:52 +00001571 @SkylarkSignature(
1572 name = "tuple",
1573 returnType = Tuple.class,
1574 doc =
Vladimir Moskvad200daf2016-12-23 16:35:37 +00001575 "Converts a collection (e.g. list, tuple or dictionary) to a tuple."
Googlerde689132016-12-12 18:15:52 +00001576 + "<pre class=\"language-python\">tuple([1, 2]) == (1, 2)\n"
Vladimir Moskvad200daf2016-12-23 16:35:37 +00001577 + "tuple((2, 3, 2)) == (2, 3, 2)\n"
Googlerde689132016-12-12 18:15:52 +00001578 + "tuple({5: \"a\", 2: \"b\", 4: \"c\"}) == (5, 2, 4)</pre>",
1579 parameters = {@Param(name = "x", doc = "The object to convert.")},
laurentlbc9b6f4a2017-06-21 11:58:50 +02001580 useLocation = true,
1581 useEnvironment = true
Googlerde689132016-12-12 18:15:52 +00001582 )
1583 private static final BuiltinFunction tuple =
1584 new BuiltinFunction("tuple") {
laurentlbc9b6f4a2017-06-21 11:58:50 +02001585 public Tuple<?> invoke(Object x, Location loc, Environment env) throws EvalException {
brandjon0528d5d2017-08-04 16:00:56 +02001586 return Tuple.copyOf(EvalUtils.toCollection(x, loc, env));
Googlerde689132016-12-12 18:15:52 +00001587 }
1588 };
1589
1590 @SkylarkSignature(
1591 name = "list",
1592 returnType = MutableList.class,
1593 doc =
Vladimir Moskvad200daf2016-12-23 16:35:37 +00001594 "Converts a collection (e.g. list, tuple or dictionary) to a list."
Googlerde689132016-12-12 18:15:52 +00001595 + "<pre class=\"language-python\">list([1, 2]) == [1, 2]\n"
Vladimir Moskvad200daf2016-12-23 16:35:37 +00001596 + "list((2, 3, 2)) == [2, 3, 2]\n"
Googlerde689132016-12-12 18:15:52 +00001597 + "list({5: \"a\", 2: \"b\", 4: \"c\"}) == [5, 2, 4]</pre>",
1598 parameters = {@Param(name = "x", doc = "The object to convert.")},
1599 useLocation = true,
1600 useEnvironment = true
1601 )
1602 private static final BuiltinFunction list =
1603 new BuiltinFunction("list") {
1604 public MutableList<?> invoke(Object x, Location loc, Environment env) throws EvalException {
michajloff50f282017-10-05 20:02:51 +02001605 return MutableList.copyOf(env, EvalUtils.toCollection(x, loc, env));
Googlerde689132016-12-12 18:15:52 +00001606 }
1607 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001608
Laurent Le Brun3a837472015-12-22 17:58:40 +00001609 @SkylarkSignature(
1610 name = "len",
1611 returnType = Integer.class,
Vladimir Moskvad200daf2016-12-23 16:35:37 +00001612 doc = "Returns the length of a string, list, tuple, depset, or dictionary.",
Damien Martin-Guillerez014388c2016-06-14 10:28:31 +00001613 parameters = {@Param(name = "x", doc = "The object to check length of.")},
laurentlbc32efc82017-06-23 16:03:00 +02001614 useLocation = true,
1615 useEnvironment = true
Laurent Le Brun3a837472015-12-22 17:58:40 +00001616 )
Laurent Le Brunb525bee2016-03-07 17:14:10 +00001617 private static final BuiltinFunction len =
Laurent Le Brun3a837472015-12-22 17:58:40 +00001618 new BuiltinFunction("len") {
laurentlbc32efc82017-06-23 16:03:00 +02001619 public Integer invoke(Object x, Location loc, Environment env) throws EvalException {
brandjon3c161912017-10-05 05:06:05 +02001620 if (env.getSemantics().incompatibleDepsetIsNotIterable()
1621 && x instanceof SkylarkNestedSet) {
laurentlbc32efc82017-06-23 16:03:00 +02001622 throw new EvalException(
1623 loc,
1624 EvalUtils.getDataTypeName(x)
brandjonf5b8d6f2017-06-23 18:03:28 +02001625 + " is not iterable. You may use `len(<depset>.to_list())` instead. Use "
1626 + "--incompatible_depset_is_not_iterable=false to temporarily disable this "
1627 + "check.");
laurentlbc32efc82017-06-23 16:03:00 +02001628 }
Laurent Le Brun3a837472015-12-22 17:58:40 +00001629 int l = EvalUtils.size(x);
1630 if (l == -1) {
1631 throw new EvalException(loc, EvalUtils.getDataTypeName(x) + " is not iterable");
1632 }
1633 return l;
1634 }
1635 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001636
vladmos46907932017-06-30 14:01:45 +02001637 @SkylarkSignature(
1638 name = "str",
1639 returnType = String.class,
1640 doc =
1641 "Converts any object to string. This is useful for debugging."
laurentlbca929032017-08-03 12:20:46 +02001642 + "<pre class=\"language-python\">str(\"ab\") == \"ab\"\n"
1643 + "str(8) == \"8\"</pre>",
vladmoscd6d8ae2017-10-12 15:35:17 +02001644 parameters = {@Param(name = "x", doc = "The object to convert.")}
vladmos46907932017-06-30 14:01:45 +02001645 )
1646 private static final BuiltinFunction str =
1647 new BuiltinFunction("str") {
vladmoscd6d8ae2017-10-12 15:35:17 +02001648 public String invoke(Object x) {
1649 return Printer.str(x);
vladmos46907932017-06-30 14:01:45 +02001650 }
1651 };
Francois-Rene Rideaud61f5312015-06-13 03:34:47 +00001652
vladmos46907932017-06-30 14:01:45 +02001653 @SkylarkSignature(
1654 name = "repr",
1655 returnType = String.class,
1656 doc =
1657 "Converts any object to a string representation. This is useful for debugging.<br>"
laurentlbca929032017-08-03 12:20:46 +02001658 + "<pre class=\"language-python\">repr(\"ab\") == '\"ab\"'</pre>",
vladmoscd6d8ae2017-10-12 15:35:17 +02001659 parameters = {@Param(name = "x", doc = "The object to convert.")}
vladmos46907932017-06-30 14:01:45 +02001660 )
1661 private static final BuiltinFunction repr =
1662 new BuiltinFunction("repr") {
vladmoscd6d8ae2017-10-12 15:35:17 +02001663 public String invoke(Object x) {
1664 return Printer.repr(x);
vladmos46907932017-06-30 14:01:45 +02001665 }
1666 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001667
laurentlb10591042017-07-20 18:16:18 +02001668 @SkylarkSignature(
1669 name = "bool",
1670 returnType = Boolean.class,
1671 doc =
1672 "Constructor for the bool type. "
1673 + "It returns <code>False</code> if the object is <code>None</code>, <code>False"
1674 + "</code>, an empty string (<code>\"\"</code>), the number <code>0</code>, or an "
1675 + "empty collection (e.g. <code>()</code>, <code>[]</code>). "
1676 + "Otherwise, it returns <code>True</code>.",
1677 parameters = {@Param(name = "x", doc = "The variable to convert.")}
1678 )
1679 private static final BuiltinFunction bool =
1680 new BuiltinFunction("bool") {
1681 public Boolean invoke(Object x) throws EvalException {
1682 return EvalUtils.toBoolean(x);
1683 }
1684 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001685
Florian Weikertbc1ff692016-07-01 19:11:24 +00001686 @SkylarkSignature(
1687 name = "int",
1688 returnType = Integer.class,
1689 doc =
1690 "Converts a value to int. "
1691 + "If the argument is a string, it is converted using the given base and raises an "
1692 + "error if the conversion fails. "
1693 + "The base can be between 2 and 36 (inclusive) and defaults to 10. "
1694 + "The value can be prefixed with 0b/0o/ox to represent values in base 2/8/16. "
1695 + "If such a prefix is present, a base of 0 can be used to automatically determine the "
1696 + "correct base: "
1697 + "<pre class=\"language-python\">int(\"0xFF\", 0) == int(\"0xFF\", 16) == 255</pre>"
1698 + "If the argument is a bool, it returns 0 (False) or 1 (True). "
1699 + "If the argument is an int, it is simply returned."
1700 + "<pre class=\"language-python\">int(\"123\") == 123</pre>",
1701 parameters = {
1702 @Param(name = "x", type = Object.class, doc = "The string to convert."),
1703 @Param(
1704 name = "base",
brandjon12b23792017-09-05 21:39:37 +02001705 type = Object.class,
1706 defaultValue = "unbound",
1707 doc = "The base to use to interpret a string value; defaults to 10. This parameter must "
1708 + "not be supplied if the value is not a string."
Florian Weikertbc1ff692016-07-01 19:11:24 +00001709 )
1710 },
1711 useLocation = true
1712 )
1713 private static final BuiltinFunction int_ =
1714 new BuiltinFunction("int") {
1715 private final ImmutableMap<String, Integer> intPrefixes =
1716 ImmutableMap.of("0b", 2, "0o", 8, "0x", 16);
1717
1718 @SuppressWarnings("unused")
brandjon12b23792017-09-05 21:39:37 +02001719 public Integer invoke(Object x, Object base, Location loc) throws EvalException {
Florian Weikertbc1ff692016-07-01 19:11:24 +00001720 if (x instanceof String) {
brandjon12b23792017-09-05 21:39:37 +02001721 if (base == Runtime.UNBOUND) {
1722 base = 10;
1723 } else if (!(base instanceof Integer)) {
1724 throw new EvalException(
1725 loc, "base must be an integer (got '" + EvalUtils.getDataTypeName(base) + "')");
1726 }
1727 return fromString((String) x, loc, (Integer) base);
Florian Weikertbc1ff692016-07-01 19:11:24 +00001728 } else {
brandjon12b23792017-09-05 21:39:37 +02001729 if (base != Runtime.UNBOUND) {
Florian Weikertbc1ff692016-07-01 19:11:24 +00001730 throw new EvalException(loc, "int() can't convert non-string with explicit base");
1731 }
1732 if (x instanceof Boolean) {
1733 return ((Boolean) x).booleanValue() ? 1 : 0;
1734 } else if (x instanceof Integer) {
1735 return (Integer) x;
1736 }
1737 throw new EvalException(
1738 loc, Printer.format("%r is not of type string or int or bool", x));
1739 }
Laurent Le Brunf4648de2015-05-07 14:00:32 +00001740 }
Florian Weikertbc1ff692016-07-01 19:11:24 +00001741
brandjon12b23792017-09-05 21:39:37 +02001742 private int fromString(String string, Location loc, int base) throws EvalException {
1743 String prefix = getIntegerPrefix(string);
1744 String digits;
1745 if (prefix == null) {
1746 // Nothing to strip. Infer base 10 if it was unknown (0).
1747 digits = string;
1748 if (base == 0) {
1749 base = 10;
1750 }
1751 } else {
1752 // Strip prefix. Infer base from prefix if unknown (0), or else verify its consistency.
1753 digits = string.substring(prefix.length());
Florian Weikertbc1ff692016-07-01 19:11:24 +00001754 int expectedBase = intPrefixes.get(prefix);
1755 if (base == 0) {
Florian Weikertbc1ff692016-07-01 19:11:24 +00001756 base = expectedBase;
1757 } else if (base != expectedBase) {
1758 throw new EvalException(
brandjon12b23792017-09-05 21:39:37 +02001759 loc, Printer.format("invalid literal for int() with base %d: %r", base, string));
Florian Weikertbc1ff692016-07-01 19:11:24 +00001760 }
1761 }
1762
1763 if (base < 2 || base > 36) {
1764 throw new EvalException(loc, "int() base must be >= 2 and <= 36");
1765 }
1766 try {
brandjon12b23792017-09-05 21:39:37 +02001767 return Integer.parseInt(digits, base);
Florian Weikertbc1ff692016-07-01 19:11:24 +00001768 } catch (NumberFormatException e) {
1769 throw new EvalException(
brandjon12b23792017-09-05 21:39:37 +02001770 loc, Printer.format("invalid literal for int() with base %d: %r", base, string));
Florian Weikertbc1ff692016-07-01 19:11:24 +00001771 }
1772 }
1773
brandjon12b23792017-09-05 21:39:37 +02001774 @Nullable
Florian Weikertbc1ff692016-07-01 19:11:24 +00001775 private String getIntegerPrefix(String value) {
1776 value = value.toLowerCase();
1777 for (String prefix : intPrefixes.keySet()) {
1778 if (value.startsWith(prefix)) {
1779 return prefix;
1780 }
1781 }
brandjon12b23792017-09-05 21:39:37 +02001782 return null;
Florian Weikertbc1ff692016-07-01 19:11:24 +00001783 }
1784 };
Laurent Le Brun0c44aa42015-04-02 11:32:47 +00001785
Laurent Le Brunc2fca382015-10-16 11:46:43 +00001786 @SkylarkSignature(
Dmitry Lomov8b1a0942015-11-19 15:14:15 +00001787 name = "dict",
Francois-Rene Rideauab049e02016-02-17 16:13:46 +00001788 returnType = SkylarkDict.class,
Dmitry Lomov8b1a0942015-11-19 15:14:15 +00001789 doc =
Jon Brandveind9d20f72017-01-27 15:20:23 +00001790 "Creates a <a href=\"dict.html\">dictionary</a> from an optional positional "
Jon Brandveindf6bbec2017-01-28 02:20:31 +00001791 + "argument and an optional set of keyword arguments. In the case where the same key "
1792 + "is given multiple times, the last value will be used. Entries supplied via keyword "
1793 + "arguments are considered to come after entries supplied via the positional "
1794 + "argument. Note that the iteration order for dictionaries is deterministic but "
1795 + "unspecified, and not necessarily related to the order in which keys are given to "
1796 + "this function.",
Damien Martin-Guillerez014388c2016-06-14 10:28:31 +00001797 parameters = {
Dmitry Lomov8b1a0942015-11-19 15:14:15 +00001798 @Param(
1799 name = "args",
1800 type = Object.class,
1801 defaultValue = "[]",
1802 doc =
1803 "Either a dictionary or a list of entries. Entries must be tuples or lists with "
Jon Brandveindf6bbec2017-01-28 02:20:31 +00001804 + "exactly two elements: key, value."
Dmitry Lomov8b1a0942015-11-19 15:14:15 +00001805 ),
1806 },
Damien Martin-Guillereze3108c52016-06-08 08:54:45 +00001807 extraKeywords = @Param(name = "kwargs", doc = "Dictionary of additional entries."),
Vladimir Moskvad200daf2016-12-23 16:35:37 +00001808 useLocation = true,
1809 useEnvironment = true
Dmitry Lomov8b1a0942015-11-19 15:14:15 +00001810 )
1811 private static final BuiltinFunction dict =
1812 new BuiltinFunction("dict") {
brandjonc06e7462017-07-11 20:54:58 +02001813 public SkylarkDict<?, ?> invoke(
Vladimir Moskvad200daf2016-12-23 16:35:37 +00001814 Object args, SkylarkDict<String, Object> kwargs, Location loc, Environment env)
Dmitry Lomov8b1a0942015-11-19 15:14:15 +00001815 throws EvalException {
brandjonc06e7462017-07-11 20:54:58 +02001816 SkylarkDict<?, ?> argsDict =
Vladimir Moskvad200daf2016-12-23 16:35:37 +00001817 (args instanceof SkylarkDict)
brandjonc06e7462017-07-11 20:54:58 +02001818 ? (SkylarkDict<?, ?>) args
Vladimir Moskvad200daf2016-12-23 16:35:37 +00001819 : getDictFromArgs(args, loc, env);
Francois-Rene Rideauab049e02016-02-17 16:13:46 +00001820 return SkylarkDict.plus(argsDict, kwargs, env);
Florian Weikerta6dae6b2015-08-04 20:17:23 +00001821 }
Dmitry Lomov8b1a0942015-11-19 15:14:15 +00001822
Francois-Rene Rideauab049e02016-02-17 16:13:46 +00001823 private SkylarkDict<Object, Object> getDictFromArgs(
Vladimir Moskvad200daf2016-12-23 16:35:37 +00001824 Object args, Location loc, Environment env) throws EvalException {
Francois-Rene Rideauab049e02016-02-17 16:13:46 +00001825 SkylarkDict<Object, Object> result = SkylarkDict.of(env);
Dmitry Lomov8b1a0942015-11-19 15:14:15 +00001826 int pos = 0;
1827 for (Object element : Type.OBJECT_LIST.convert(args, "parameter args in dict()")) {
1828 List<Object> pair = convertToPair(element, pos, loc);
Francois-Rene Rideauab049e02016-02-17 16:13:46 +00001829 result.put(pair.get(0), pair.get(1), loc, env);
Dmitry Lomov8b1a0942015-11-19 15:14:15 +00001830 ++pos;
1831 }
1832 return result;
1833 }
1834
1835 private List<Object> convertToPair(Object element, int pos, Location loc)
1836 throws EvalException {
1837 try {
1838 List<Object> tuple = Type.OBJECT_LIST.convert(element, "");
1839 int numElements = tuple.size();
1840 if (numElements != 2) {
1841 throw new EvalException(
1842 location,
1843 String.format(
Laurent Le Brunc31f3512016-12-29 21:41:33 +00001844 "item #%d has length %d, but exactly two elements are required",
Vladimir Moskvad200daf2016-12-23 16:35:37 +00001845 pos, numElements));
Dmitry Lomov8b1a0942015-11-19 15:14:15 +00001846 }
1847 return tuple;
1848 } catch (ConversionException e) {
1849 throw new EvalException(
Laurent Le Brunc31f3512016-12-29 21:41:33 +00001850 loc, String.format("cannot convert item #%d to a sequence", pos));
Dmitry Lomov8b1a0942015-11-19 15:14:15 +00001851 }
1852 }
1853 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001854
Laurent Le Brune51a4d22016-10-11 18:04:16 +00001855 @SkylarkSignature(
1856 name = "enumerate",
1857 returnType = MutableList.class,
1858 doc =
1859 "Returns a list of pairs (two-element tuples), with the index (int) and the item from"
1860 + " the input list.\n<pre class=\"language-python\">"
1861 + "enumerate([24, 21, 84]) == [(0, 24), (1, 21), (2, 84)]</pre>\n",
John Caterd7928422017-01-03 20:06:59 +00001862 parameters = {@Param(name = "list", type = SkylarkList.class, doc = "input list.")},
Laurent Le Brune51a4d22016-10-11 18:04:16 +00001863 useEnvironment = true
1864 )
1865 private static final BuiltinFunction enumerate =
1866 new BuiltinFunction("enumerate") {
1867 public MutableList<?> invoke(SkylarkList<?> input, Environment env) throws EvalException {
1868 int count = 0;
michajlo490eb972017-10-16 21:30:13 +02001869 ArrayList<SkylarkList<?>> result = new ArrayList<>(input.size());
Laurent Le Brune51a4d22016-10-11 18:04:16 +00001870 for (Object obj : input) {
1871 result.add(Tuple.of(count, obj));
1872 count++;
1873 }
michajlo490eb972017-10-16 21:30:13 +02001874 return MutableList.wrapUnsafe(env, result);
Laurent Le Brune51a4d22016-10-11 18:04:16 +00001875 }
1876 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001877
John Caterd7928422017-01-03 20:06:59 +00001878 @SkylarkSignature(
1879 name = "hash",
1880 returnType = Integer.class,
1881 doc =
1882 "Return a hash value for a string. This is computed deterministically using the same "
1883 + "algorithm as Java's <code>String.hashCode()</code>, namely: "
Chaoren Lin905914e2017-02-06 19:18:44 +00001884 + "<pre class=\"language-python\">s[0] * (31^(n-1)) + s[1] * (31^(n-2)) + ... + s[n-1]"
John Caterd7928422017-01-03 20:06:59 +00001885 + "</pre> Hashing of values besides strings is not currently supported.",
1886 // Deterministic hashing is important for the consistency of builds, hence why we
1887 // promise a specific algorithm. This is in contrast to Java (Object.hashCode()) and
1888 // Python, which promise stable hashing only within a given execution of the program.
1889 parameters = {@Param(name = "value", type = String.class, doc = "String value to hash.")}
1890 )
1891 private static final BuiltinFunction hash =
1892 new BuiltinFunction("hash") {
1893 public Integer invoke(String value) throws EvalException {
1894 return value.hashCode();
1895 }
1896 };
Jon Brandvein9c4629d2016-07-20 20:16:33 +00001897
Laurent Le Brune51a4d22016-10-11 18:04:16 +00001898 @SkylarkSignature(
1899 name = "range",
1900 returnType = MutableList.class,
1901 doc =
1902 "Creates a list where items go from <code>start</code> to <code>stop</code>, using a "
1903 + "<code>step</code> increment. If a single argument is provided, items will "
1904 + "range from 0 to that element."
1905 + "<pre class=\"language-python\">range(4) == [0, 1, 2, 3]\n"
1906 + "range(3, 9, 2) == [3, 5, 7]\n"
1907 + "range(3, 0, -1) == [3, 2, 1]</pre>",
1908 parameters = {
1909 @Param(
1910 name = "start_or_stop",
1911 type = Integer.class,
1912 doc =
1913 "Value of the start element if stop is provided, "
1914 + "otherwise value of stop and the actual start is 0"
1915 ),
1916 @Param(
1917 name = "stop_or_none",
1918 type = Integer.class,
1919 noneable = true,
1920 defaultValue = "None",
1921 doc =
1922 "optional index of the first item <i>not</i> to be included in the resulting "
1923 + "list; generation of the list stops before <code>stop</code> is reached."
1924 ),
1925 @Param(
1926 name = "step",
1927 type = Integer.class,
1928 defaultValue = "1",
1929 doc = "The increment (default is 1). It may be negative."
1930 )
1931 },
1932 useLocation = true,
1933 useEnvironment = true
1934 )
1935 private static final BuiltinFunction range =
1936 new BuiltinFunction("range") {
1937 public MutableList<?> invoke(
1938 Integer startOrStop, Object stopOrNone, Integer step, Location loc, Environment env)
1939 throws EvalException {
1940 int start;
1941 int stop;
1942 if (stopOrNone == Runtime.NONE) {
1943 start = 0;
1944 stop = startOrStop;
1945 } else {
1946 start = startOrStop;
1947 stop = Type.INTEGER.convert(stopOrNone, "'stop' operand of 'range'");
1948 }
1949 if (step == 0) {
1950 throw new EvalException(loc, "step cannot be 0");
1951 }
michajlo490eb972017-10-16 21:30:13 +02001952 ArrayList<Integer> result = new ArrayList<>(Math.abs((stop - start) / step));
Laurent Le Brune51a4d22016-10-11 18:04:16 +00001953 if (step > 0) {
Laurent Le Brune51a4d22016-10-11 18:04:16 +00001954 while (start < stop) {
1955 result.add(start);
1956 start += step;
1957 }
1958 } else {
Laurent Le Brune51a4d22016-10-11 18:04:16 +00001959 while (start > stop) {
1960 result.add(start);
1961 start += step;
1962 }
1963 }
michajlo490eb972017-10-16 21:30:13 +02001964 return MutableList.wrapUnsafe(env, result);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001965 }
Laurent Le Brune51a4d22016-10-11 18:04:16 +00001966 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001967
Laurent Le Brune51a4d22016-10-11 18:04:16 +00001968 /** Returns true if the object has a field of the given name, otherwise false. */
1969 @SkylarkSignature(
1970 name = "hasattr",
1971 returnType = Boolean.class,
1972 doc =
1973 "Returns True if the object <code>x</code> has an attribute or method of the given "
1974 + "<code>name</code>, otherwise False. Example:<br>"
1975 + "<pre class=\"language-python\">hasattr(ctx.attr, \"myattr\")</pre>",
1976 parameters = {
1977 @Param(name = "x", doc = "The object to check."),
1978 @Param(name = "name", type = String.class, doc = "The name of the attribute.")
1979 },
Laurent Le Brune51a4d22016-10-11 18:04:16 +00001980 useEnvironment = true
1981 )
1982 private static final BuiltinFunction hasattr =
1983 new BuiltinFunction("hasattr") {
1984 @SuppressWarnings("unused")
Jon Brandveind9d20f72017-01-27 15:20:23 +00001985 public Boolean invoke(Object obj, String name, Environment env)
Laurent Le Brune51a4d22016-10-11 18:04:16 +00001986 throws EvalException {
1987 if (obj instanceof ClassObject && ((ClassObject) obj).getValue(name) != null) {
1988 return true;
1989 }
Jon Brandveind9d20f72017-01-27 15:20:23 +00001990 return hasMethod(obj, name);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001991 }
Laurent Le Brune51a4d22016-10-11 18:04:16 +00001992 };
1993
1994 @SkylarkSignature(
1995 name = "getattr",
1996 doc =
1997 "Returns the struct's field of the given name if it exists. If not, it either returns "
Jon Brandvein29bb6622016-10-27 13:55:43 +00001998 + "<code>default</code> (if specified) or raises an error. Built-in methods cannot "
1999 + "currently be retrieved in this way; doing so will result in an error if a "
2000 + "<code>default</code> is not given. <code>getattr(x, \"foobar\")</code> is "
2001 + "equivalent to <code>x.foobar</code>."
Laurent Le Brune51a4d22016-10-11 18:04:16 +00002002 + "<pre class=\"language-python\">getattr(ctx.attr, \"myattr\")\n"
2003 + "getattr(ctx.attr, \"myattr\", \"mydefault\")</pre>",
2004 parameters = {
2005 @Param(name = "x", doc = "The struct whose attribute is accessed."),
2006 @Param(name = "name", doc = "The name of the struct attribute."),
2007 @Param(
2008 name = "default",
2009 defaultValue = "unbound",
2010 doc =
2011 "The default value to return in case the struct "
2012 + "doesn't have an attribute of the given name."
2013 )
2014 },
2015 useLocation = true,
2016 useEnvironment = true
2017 )
2018 private static final BuiltinFunction getattr =
2019 new BuiltinFunction("getattr") {
2020 @SuppressWarnings("unused")
2021 public Object invoke(
2022 Object obj, String name, Object defaultValue, Location loc, Environment env)
2023 throws EvalException {
2024 Object result = DotExpression.eval(obj, name, loc, env);
2025 if (result == null) {
2026 // 'Real' describes methods with structField() == false. Because DotExpression.eval
2027 // returned null in this case, we know that structField() cannot return true.
Jon Brandveind9d20f72017-01-27 15:20:23 +00002028 boolean isRealMethod = hasMethod(obj, name);
Jon Brandvein29bb6622016-10-27 13:55:43 +00002029 if (defaultValue != Runtime.UNBOUND) {
Laurent Le Brune51a4d22016-10-11 18:04:16 +00002030 return defaultValue;
2031 }
2032 throw new EvalException(
2033 loc,
2034 Printer.format(
Laurent Le Brunc31f3512016-12-29 21:41:33 +00002035 "object of type '%s' has no attribute %r%s",
Laurent Le Brune51a4d22016-10-11 18:04:16 +00002036 EvalUtils.getDataTypeName(obj),
2037 name,
2038 isRealMethod ? ", however, a method of that name exists" : ""));
2039 }
2040 return result;
2041 }
2042 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01002043
Florian Weikerte5e3e912016-03-08 03:08:26 +00002044 /**
2045 * Returns whether the given object has a method with the given name.
2046 */
Jon Brandveind9d20f72017-01-27 15:20:23 +00002047 private static boolean hasMethod(Object obj, String name) throws EvalException {
brandjondc2c5502017-12-07 14:30:04 -08002048 if (Runtime.getBuiltinRegistry().getFunctionNames(obj.getClass()).contains(name)) {
Florian Weikerte5e3e912016-03-08 03:08:26 +00002049 return true;
2050 }
2051
Laurent Le Brun57badf42017-01-02 15:12:24 +00002052 return FuncallExpression.getMethodNames(obj.getClass()).contains(name);
Florian Weikerte5e3e912016-03-08 03:08:26 +00002053 }
2054
Laurent Le Brune51a4d22016-10-11 18:04:16 +00002055 @SkylarkSignature(
2056 name = "dir",
2057 returnType = MutableList.class,
2058 doc =
vladmos6547bde2017-04-25 23:07:04 +02002059 "Returns a list of strings: the names of the attributes and "
Laurent Le Brune51a4d22016-10-11 18:04:16 +00002060 + "methods of the parameter object.",
2061 parameters = {@Param(name = "x", doc = "The object to check.")},
2062 useLocation = true,
2063 useEnvironment = true
2064 )
2065 private static final BuiltinFunction dir =
2066 new BuiltinFunction("dir") {
2067 public MutableList<?> invoke(Object object, Location loc, Environment env)
2068 throws EvalException {
2069 // Order the fields alphabetically.
2070 Set<String> fields = new TreeSet<>();
2071 if (object instanceof ClassObject) {
brandjond331fa72017-12-28 07:38:31 -08002072 fields.addAll(((ClassObject) object).getFieldNames());
Laurent Le Brune51a4d22016-10-11 18:04:16 +00002073 }
brandjondc2c5502017-12-07 14:30:04 -08002074 fields.addAll(Runtime.getBuiltinRegistry().getFunctionNames(object.getClass()));
Laurent Le Brun57badf42017-01-02 15:12:24 +00002075 fields.addAll(FuncallExpression.getMethodNames(object.getClass()));
michajloff50f282017-10-05 20:02:51 +02002076 return MutableList.copyOf(env, fields);
Laurent Le Brune51a4d22016-10-11 18:04:16 +00002077 }
2078 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01002079
Laurent Le Brunf9c41022016-06-23 16:05:50 +00002080 @SkylarkSignature(
Laurent Le Brunfe206a42016-05-23 17:03:49 +00002081 name = "fail",
2082 doc =
2083 "Raises an error that cannot be intercepted. It can be used anywhere, "
2084 + "both in the loading phase and in the analysis phase.",
2085 returnType = Runtime.NoneType.class,
Damien Martin-Guillerez014388c2016-06-14 10:28:31 +00002086 parameters = {
Laurent Le Brunfe206a42016-05-23 17:03:49 +00002087 @Param(
2088 name = "msg",
2089 type = Object.class,
2090 doc = "Error to display for the user. The object is converted to a string."
Damien Martin-Guillerez014388c2016-06-14 10:28:31 +00002091 ),
Laurent Le Brunfe206a42016-05-23 17:03:49 +00002092 @Param(
2093 name = "attr",
2094 type = String.class,
2095 noneable = true,
2096 defaultValue = "None",
2097 doc =
2098 "The name of the attribute that caused the error. This is used only for "
2099 + "error reporting."
2100 )
2101 },
2102 useLocation = true
2103 )
2104 private static final BuiltinFunction fail =
2105 new BuiltinFunction("fail") {
Laurent Le Brune51a4d22016-10-11 18:04:16 +00002106 public Runtime.NoneType invoke(Object msg, Object attr, Location loc) throws EvalException {
Laurent Le Brunfe206a42016-05-23 17:03:49 +00002107 String str = Printer.str(msg);
2108 if (attr != Runtime.NONE) {
2109 str = String.format("attribute %s: %s", attr, str);
2110 }
2111 throw new EvalException(loc, str);
2112 }
2113 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01002114
laurentlb3d2a68c2017-06-30 00:32:04 +02002115 @SkylarkSignature(
2116 name = "print",
2117 returnType = Runtime.NoneType.class,
2118 doc =
cparsons7ec3f212018-02-16 14:21:10 -08002119 "Prints <code>args</code> as debug output. It will be prefixed with the string <code>"
2120 + "\"DEBUG\"</code> and the location (file and line number) of this call. The "
2121 + "exact way in which the arguments are converted to strings is unspecified and may "
2122 + "change at any time. In particular, it may be different from (and more detailed "
2123 + "than) the formatting done by <a href='#str'><code>str()</code></a> and <a "
2124 + "href='#repr'><code>repr()</code></a>."
laurentlb3d2a68c2017-06-30 00:32:04 +02002125 + "<p>Using <code>print</code> in production code is discouraged due to the spam it "
2126 + "creates for users. For deprecations, prefer a hard error using <a href=\"#fail\">"
cparsons7ec3f212018-02-16 14:21:10 -08002127 + "<code>fail()</code></a> whenever possible.",
laurentlb3d2a68c2017-06-30 00:32:04 +02002128 parameters = {
2129 @Param(
2130 name = "sep",
2131 type = String.class,
laurentlbd1e564b2017-07-19 21:18:24 +02002132 defaultValue = "\" \"",
laurentlb3d2a68c2017-06-30 00:32:04 +02002133 named = true,
2134 positional = false,
2135 doc = "The separator string between the objects, default is space (\" \")."
2136 )
2137 },
2138 // NB: as compared to Python3, we're missing optional named-only arguments 'end' and 'file'
2139 extraPositionals = @Param(name = "args", doc = "The objects to print."),
2140 useLocation = true,
2141 useEnvironment = true
2142 )
2143 private static final BuiltinFunction print =
2144 new BuiltinFunction("print") {
2145 public Runtime.NoneType invoke(
2146 String sep, SkylarkList<?> starargs, Location loc, Environment env)
2147 throws EvalException {
cparsons7ec3f212018-02-16 14:21:10 -08002148 String msg = starargs.stream().map(Printer::debugPrint).collect(joining(sep));
laurentlb3d2a68c2017-06-30 00:32:04 +02002149 // As part of the integration test "skylark_flag_test.sh", if the
2150 // "--internal_skylark_flag_test_canary" flag is enabled, append an extra marker string to
vladmos6ff634d2017-07-05 10:25:01 -04002151 // the output.
brandjon3c161912017-10-05 05:06:05 +02002152 if (env.getSemantics().internalSkylarkFlagTestCanary()) {
laurentlb3d2a68c2017-06-30 00:32:04 +02002153 msg += "<== skylark flag test ==>";
2154 }
vladmos72d51092018-03-22 03:54:13 -07002155 env.handleEvent(Event.debug(loc, msg));
laurentlb3d2a68c2017-06-30 00:32:04 +02002156 return Runtime.NONE;
2157 }
2158 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01002159
John Caterd7928422017-01-03 20:06:59 +00002160 @SkylarkSignature(
2161 name = "zip",
2162 doc =
2163 "Returns a <code>list</code> of <code>tuple</code>s, where the i-th tuple contains "
2164 + "the i-th element from each of the argument sequences or iterables. The list has the "
2165 + "size of the shortest input. With a single iterable argument, it returns a list of "
2166 + "1-tuples. With no arguments, it returns an empty list. Examples:"
2167 + "<pre class=\"language-python\">"
2168 + "zip() # == []\n"
2169 + "zip([1, 2]) # == [(1,), (2,)]\n"
2170 + "zip([1, 2], [3, 4]) # == [(1, 3), (2, 4)]\n"
2171 + "zip([1, 2], [3, 4, 5]) # == [(1, 3), (2, 4)]</pre>",
2172 extraPositionals = @Param(name = "args", doc = "lists to zip."),
2173 returnType = MutableList.class,
2174 useLocation = true,
2175 useEnvironment = true
2176 )
2177 private static final BuiltinFunction zip =
2178 new BuiltinFunction("zip") {
2179 public MutableList<?> invoke(SkylarkList<?> args, Location loc, Environment env)
2180 throws EvalException {
2181 Iterator<?>[] iterators = new Iterator<?>[args.size()];
2182 for (int i = 0; i < args.size(); i++) {
laurentlbc9b6f4a2017-06-21 11:58:50 +02002183 iterators[i] = EvalUtils.toIterable(args.get(i), loc, env).iterator();
Googlerc60ec8c2015-03-23 14:20:18 +00002184 }
michajlo490eb972017-10-16 21:30:13 +02002185 ArrayList<Tuple<?>> result = new ArrayList<>();
John Caterd7928422017-01-03 20:06:59 +00002186 boolean allHasNext;
2187 do {
2188 allHasNext = !args.isEmpty();
2189 List<Object> elem = Lists.newArrayListWithExpectedSize(args.size());
2190 for (Iterator<?> iterator : iterators) {
2191 if (iterator.hasNext()) {
2192 elem.add(iterator.next());
2193 } else {
2194 allHasNext = false;
2195 }
2196 }
2197 if (allHasNext) {
2198 result.add(Tuple.copyOf(elem));
2199 }
2200 } while (allHasNext);
michajlo490eb972017-10-16 21:30:13 +02002201 return MutableList.wrapUnsafe(env, result);
Googlerc60ec8c2015-03-23 14:20:18 +00002202 }
John Caterd7928422017-01-03 20:06:59 +00002203 };
Googlerc60ec8c2015-03-23 14:20:18 +00002204
Dmitry Lomov34cdae32016-06-28 16:13:35 +00002205 /** Skylark String module. */
Florian Weikerte3421962015-12-17 12:46:08 +00002206 @SkylarkModule(
2207 name = "string",
Dmitry Lomov34cdae32016-06-28 16:13:35 +00002208 category = SkylarkModuleCategory.BUILTIN,
Florian Weikerte3421962015-12-17 12:46:08 +00002209 doc =
2210 "A language built-in type to support strings. "
2211 + "Examples of string literals:<br>"
2212 + "<pre class=\"language-python\">a = 'abc\\ndef'\n"
2213 + "b = \"ab'cd\"\n"
2214 + "c = \"\"\"multiline string\"\"\"\n"
2215 + "\n"
2216 + "# Strings support slicing (negative index starts from the end):\n"
2217 + "x = \"hello\"[2:4] # \"ll\"\n"
2218 + "y = \"hello\"[1:-1] # \"ell\"\n"
2219 + "z = \"hello\"[:4] # \"hell\""
2220 + "# Slice steps can be used, too:\n"
2221 + "s = \"hello\"[::2] # \"hlo\"\n"
2222 + "t = \"hello\"[3:0:-1] # \"lle\"\n</pre>"
2223 + "Strings are iterable and support the <code>in</code> operator. Examples:<br>"
2224 + "<pre class=\"language-python\">\"bc\" in \"abcd\" # evaluates to True\n"
2225 + "x = [s for s in \"abc\"] # x == [\"a\", \"b\", \"c\"]</pre>\n"
2226 + "Implicit concatenation of strings is not allowed; use the <code>+</code> "
2227 + "operator instead."
2228 )
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +00002229 static final class StringModule {}
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01002230
Laurent Le Brun4f3b5822017-02-15 16:20:24 +00002231 /** Skylark int type. */
2232 @SkylarkModule(
2233 name = "int",
2234 category = SkylarkModuleCategory.BUILTIN,
2235 doc =
2236 "A type to represent integers. It can represent any number between -2147483648 and "
2237 + "2147483647 (included). "
2238 + "Examples of int values:<br>"
2239 + "<pre class=\"language-python\">"
2240 + "153\n"
2241 + "0x2A # hexadecimal literal\n"
2242 + "054 # octal literal\n"
2243 + "23 * 2 + 5\n"
2244 + "100 / -7\n"
2245 + "100 % -7 # -5 (unlike in some other languages)\n"
2246 + "int(\"18\")\n"
2247 + "</pre>"
2248 )
2249 public static final class IntModule {}
2250
2251 /** Skylark bool type. */
2252 @SkylarkModule(
2253 name = "bool",
2254 category = SkylarkModuleCategory.BUILTIN,
2255 doc =
2256 "A type to represent booleans. There are only two possible values: "
2257 + "<a href=\"globals.html#True\">True</a> and "
2258 + "<a href=\"globals.html#False\">False</a>. "
2259 + "Any value can be converted to a boolean using the "
2260 + "<a href=\"globals.html#bool\">bool</a> function."
2261 )
2262 public static final class BoolModule {}
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01002263
Laurent Le Brun5e991982016-10-14 13:39:45 +00002264 static final List<BaseFunction> defaultGlobalFunctions =
brandjonc06e7462017-07-11 20:54:58 +02002265 ImmutableList.of(
Laurent Le Brun5e991982016-10-14 13:39:45 +00002266 all, any, bool, dict, dir, fail, getattr, hasattr, hash, enumerate, int_, len, list, max,
brandjonf2ed8582017-06-27 15:05:35 +02002267 min, print, range, repr, reversed, sorted, str, tuple, zip);
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +00002268
2269 static {
2270 SkylarkSignatureProcessor.configureSkylarkFunctions(MethodLibrary.class);
2271 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01002272}