Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1 | // Copyright 2014 Google Inc. All rights reserved. |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | |
Francois-Rene Rideau | 3679556 | 2015-07-29 18:50:50 +0000 | [diff] [blame] | 15 | package com.google.devtools.build.lib.syntax; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 16 | |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 17 | import com.google.common.base.Joiner; |
| 18 | import com.google.common.collect.ImmutableList; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 19 | import com.google.common.collect.Iterables; |
| 20 | import com.google.common.collect.Lists; |
Francois-Rene Rideau | c673a82 | 2015-03-02 19:52:39 +0000 | [diff] [blame] | 21 | import com.google.common.collect.Ordering; |
Florian Weikert | fa759e2 | 2015-06-09 12:18:46 +0000 | [diff] [blame] | 22 | import com.google.devtools.build.lib.collect.nestedset.Order; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 23 | import com.google.devtools.build.lib.events.Event; |
| 24 | import com.google.devtools.build.lib.events.Location; |
Francois-Rene Rideau | 3679556 | 2015-07-29 18:50:50 +0000 | [diff] [blame] | 25 | import com.google.devtools.build.lib.packages.Type; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 26 | import com.google.devtools.build.lib.packages.Type.ConversionException; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 27 | import com.google.devtools.build.lib.syntax.ClassObject.SkylarkClassObject; |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 28 | import com.google.devtools.build.lib.syntax.SkylarkSignature.Param; |
| 29 | import com.google.devtools.build.lib.syntax.SkylarkSignatureProcessor.HackHackEitherList; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 30 | |
Googler | c60ec8c | 2015-03-23 14:20:18 +0000 | [diff] [blame] | 31 | import java.util.ArrayList; |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 32 | import java.util.Arrays; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 33 | import java.util.Collection; |
Florian Weikert | a6dae6b | 2015-08-04 20:17:23 +0000 | [diff] [blame] | 34 | import java.util.HashMap; |
Googler | c60ec8c | 2015-03-23 14:20:18 +0000 | [diff] [blame] | 35 | import java.util.Iterator; |
Florian Weikert | 8e2e5fc | 2015-05-29 12:39:08 +0000 | [diff] [blame] | 36 | import java.util.LinkedList; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 37 | import java.util.List; |
| 38 | import java.util.Map; |
| 39 | import java.util.Set; |
Laurent Le Brun | 196c1a7 | 2015-03-18 13:03:04 +0000 | [diff] [blame] | 40 | import java.util.TreeMap; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 41 | import java.util.TreeSet; |
| 42 | import java.util.concurrent.ExecutionException; |
| 43 | import java.util.regex.Matcher; |
| 44 | import java.util.regex.Pattern; |
| 45 | |
| 46 | /** |
| 47 | * A helper class containing built in functions for the Build and the Build Extension Language. |
| 48 | */ |
| 49 | public class MethodLibrary { |
| 50 | |
| 51 | private MethodLibrary() {} |
| 52 | |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 53 | // TODO(bazel-team): |
| 54 | // the Build language and Skylark currently have different list types: |
| 55 | // the Build language uses plain java List (usually ArrayList) which is mutable and accepts |
| 56 | // any argument, whereas Skylark uses SkylarkList which is immutable and accepts only |
| 57 | // arguments of the same kind. Some methods below use HackHackEitherList as a magic marker |
| 58 | // to indicate that either kind of lists is used depending on the evaluation context. |
| 59 | // It might be a good idea to either have separate methods for the two languages where it matters, |
| 60 | // or to unify the two languages so they use the same data structure (which might require |
| 61 | // updating all existing clients). |
| 62 | |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 63 | // Convert string index in the same way Python does. |
| 64 | // If index is negative, starts from the end. |
| 65 | // If index is outside bounds, it is restricted to the valid range. |
Francois-Rene Rideau | 76023b9 | 2015-04-17 15:31:59 +0000 | [diff] [blame] | 66 | private static int clampIndex(int index, int length) { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 67 | if (index < 0) { |
Laurent Le Brun | eeef30f | 2015-03-16 15:12:35 +0000 | [diff] [blame] | 68 | index += length; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 69 | } |
Laurent Le Brun | eeef30f | 2015-03-16 15:12:35 +0000 | [diff] [blame] | 70 | return Math.max(Math.min(index, length), 0); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 71 | } |
| 72 | |
| 73 | // Emulate Python substring function |
| 74 | // It converts out of range indices, and never fails |
Francois-Rene Rideau | 76023b9 | 2015-04-17 15:31:59 +0000 | [diff] [blame] | 75 | private static String pythonSubstring(String str, int start, Object end, String msg) |
| 76 | throws ConversionException { |
| 77 | if (start == 0 && EvalUtils.isNullOrNone(end)) { |
| 78 | return str; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 79 | } |
Francois-Rene Rideau | 76023b9 | 2015-04-17 15:31:59 +0000 | [diff] [blame] | 80 | start = clampIndex(start, str.length()); |
| 81 | int stop; |
| 82 | if (EvalUtils.isNullOrNone(end)) { |
| 83 | stop = str.length(); |
| 84 | } else { |
| 85 | stop = clampIndex(Type.INTEGER.convert(end, msg), str.length()); |
| 86 | } |
| 87 | if (start >= stop) { |
| 88 | return ""; |
| 89 | } |
| 90 | return str.substring(start, stop); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 91 | } |
| 92 | |
Francois-Rene Rideau | 0f7ba34 | 2015-08-31 16:16:21 +0000 | [diff] [blame^] | 93 | private static int getListIndex(Object key, int listSize, Location loc) |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 94 | throws ConversionException, EvalException { |
| 95 | // Get the nth element in the list |
| 96 | int index = Type.INTEGER.convert(key, "index operand"); |
| 97 | if (index < 0) { |
| 98 | index += listSize; |
| 99 | } |
| 100 | if (index < 0 || index >= listSize) { |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 101 | throw new EvalException(loc, "List index out of range (index is " |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 102 | + index + ", but list has " + listSize + " elements)"); |
| 103 | } |
| 104 | return index; |
| 105 | } |
| 106 | |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 107 | // supported string methods |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 108 | |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 109 | @SkylarkSignature(name = "join", objectType = StringModule.class, returnType = String.class, |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 110 | doc = "Returns a string in which the string elements of the argument have been " |
| 111 | + "joined by this string as a separator. Example:<br>" |
Laurent Le Brun | 9d27a01 | 2015-03-31 12:28:02 +0000 | [diff] [blame] | 112 | + "<pre class=\"language-python\">\"|\".join([\"a\", \"b\", \"c\"]) == \"a|b|c\"</pre>", |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 113 | mandatoryPositionals = { |
| 114 | @Param(name = "self", type = String.class, doc = "This string, a separator."), |
| 115 | @Param(name = "elements", type = HackHackEitherList.class, doc = "The objects to join.")}) |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 116 | private static BuiltinFunction join = new BuiltinFunction("join") { |
| 117 | public String invoke(String self, Object elements) throws ConversionException { |
| 118 | List<?> seq = Type.OBJECT_LIST.convert(elements, "'join.elements' argument"); |
| 119 | return Joiner.on(self).join(seq); |
| 120 | } |
| 121 | }; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 122 | |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 123 | @SkylarkSignature(name = "lower", objectType = StringModule.class, returnType = String.class, |
| 124 | doc = "Returns the lower case version of this string.", |
| 125 | mandatoryPositionals = { |
| 126 | @Param(name = "self", type = String.class, doc = "This string, to convert to lower case.")}) |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 127 | private static BuiltinFunction lower = new BuiltinFunction("lower") { |
| 128 | public String invoke(String self) { |
| 129 | return self.toLowerCase(); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 130 | } |
| 131 | }; |
| 132 | |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 133 | @SkylarkSignature(name = "upper", objectType = StringModule.class, returnType = String.class, |
| 134 | doc = "Returns the upper case version of this string.", |
| 135 | mandatoryPositionals = { |
| 136 | @Param(name = "self", type = String.class, doc = "This string, to convert to upper case.")}) |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 137 | private static BuiltinFunction upper = new BuiltinFunction("upper") { |
| 138 | public String invoke(String self) { |
| 139 | return self.toUpperCase(); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 140 | } |
| 141 | }; |
| 142 | |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 143 | @SkylarkSignature(name = "replace", objectType = StringModule.class, returnType = String.class, |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 144 | doc = "Returns a copy of the string in which the occurrences " |
| 145 | + "of <code>old</code> have been replaced with <code>new</code>, optionally restricting " |
| 146 | + "the number of replacements to <code>maxsplit</code>.", |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 147 | mandatoryPositionals = { |
| 148 | @Param(name = "self", type = String.class, doc = "This string."), |
| 149 | @Param(name = "old", type = String.class, doc = "The string to be replaced."), |
| 150 | @Param(name = "new", type = String.class, doc = "The string to replace with.")}, |
| 151 | optionalPositionals = { |
Francois-Rene Rideau | 534dca1 | 2015-04-21 19:43:19 +0000 | [diff] [blame] | 152 | @Param(name = "maxsplit", type = Integer.class, noneable = true, defaultValue = "None", |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 153 | doc = "The maximum number of replacements.")}, |
| 154 | useLocation = true) |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 155 | private static BuiltinFunction replace = new BuiltinFunction("replace") { |
| 156 | public String invoke(String self, String oldString, String newString, Object maxSplitO, |
| 157 | Location loc) throws EvalException, ConversionException { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 158 | StringBuffer sb = new StringBuffer(); |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 159 | Integer maxSplit = Type.INTEGER.convertOptional( |
| 160 | maxSplitO, "'maxsplit' argument of 'replace'", /*label*/null, Integer.MAX_VALUE); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 161 | try { |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 162 | Matcher m = Pattern.compile(oldString, Pattern.LITERAL).matcher(self); |
| 163 | for (int i = 0; i < maxSplit && m.find(); i++) { |
| 164 | m.appendReplacement(sb, Matcher.quoteReplacement(newString)); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 165 | } |
| 166 | m.appendTail(sb); |
| 167 | } catch (IllegalStateException e) { |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 168 | throw new EvalException(loc, e.getMessage() + " in call to replace"); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 169 | } |
| 170 | return sb.toString(); |
| 171 | } |
| 172 | }; |
| 173 | |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 174 | @SkylarkSignature(name = "split", objectType = StringModule.class, |
| 175 | returnType = HackHackEitherList.class, |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 176 | doc = "Returns a list of all the words in the string, using <code>sep</code> " |
| 177 | + "as the separator, optionally limiting the number of splits to <code>maxsplit</code>.", |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 178 | mandatoryPositionals = { |
Florian Weikert | 8e2e5fc | 2015-05-29 12:39:08 +0000 | [diff] [blame] | 179 | @Param(name = "self", type = String.class, doc = "This string."), |
| 180 | @Param(name = "sep", type = String.class, doc = "The string to split on.")}, |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 181 | optionalPositionals = { |
Francois-Rene Rideau | 534dca1 | 2015-04-21 19:43:19 +0000 | [diff] [blame] | 182 | @Param(name = "maxsplit", type = Integer.class, noneable = true, defaultValue = "None", |
| 183 | doc = "The maximum number of splits.")}, |
Florian Weikert | 8e2e5fc | 2015-05-29 12:39:08 +0000 | [diff] [blame] | 184 | useEnvironment = true, |
| 185 | useLocation = true) |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 186 | private static BuiltinFunction split = new BuiltinFunction("split") { |
Laurent Le Brun | 5482074 | 2015-07-30 16:43:52 +0000 | [diff] [blame] | 187 | public Object invoke(String self, String sep, Object maxSplitO, Location loc, |
Florian Weikert | 8e2e5fc | 2015-05-29 12:39:08 +0000 | [diff] [blame] | 188 | Environment env) throws ConversionException, EvalException { |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 189 | int maxSplit = Type.INTEGER.convertOptional( |
| 190 | maxSplitO, "'split' argument of 'split'", /*label*/null, -2); |
| 191 | // + 1 because the last result is the remainder, and default of -2 so that after +1 it's -1 |
| 192 | String[] ss = Pattern.compile(sep, Pattern.LITERAL).split(self, maxSplit + 1); |
Florian Weikert | 8e2e5fc | 2015-05-29 12:39:08 +0000 | [diff] [blame] | 193 | return convert(Arrays.<String>asList(ss), env, loc); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 194 | } |
| 195 | }; |
Laurent Le Brun | 5482074 | 2015-07-30 16:43:52 +0000 | [diff] [blame] | 196 | |
Florian Weikert | 8e2e5fc | 2015-05-29 12:39:08 +0000 | [diff] [blame] | 197 | @SkylarkSignature(name = "rsplit", objectType = StringModule.class, |
| 198 | returnType = HackHackEitherList.class, |
| 199 | doc = "Returns a list of all the words in the string, using <code>sep</code> " |
| 200 | + "as the separator, optionally limiting the number of splits to <code>maxsplit</code>. " |
| 201 | + "Except for splitting from the right, this method behaves like split().", |
| 202 | mandatoryPositionals = { |
| 203 | @Param(name = "self", type = String.class, doc = "This string."), |
| 204 | @Param(name = "sep", type = String.class, doc = "The string to split on.")}, |
| 205 | optionalPositionals = { |
| 206 | @Param(name = "maxsplit", type = Integer.class, noneable = true, |
Laurent Le Brun | 5482074 | 2015-07-30 16:43:52 +0000 | [diff] [blame] | 207 | defaultValue = "None", doc = "The maximum number of splits.")}, |
Florian Weikert | 8e2e5fc | 2015-05-29 12:39:08 +0000 | [diff] [blame] | 208 | useEnvironment = true, |
| 209 | useLocation = true) |
| 210 | private static BuiltinFunction rsplit = new BuiltinFunction("rsplit") { |
| 211 | @SuppressWarnings("unused") |
| 212 | public Object invoke(String self, String sep, Object maxSplitO, Location loc, Environment env) |
| 213 | throws ConversionException, EvalException { |
| 214 | int maxSplit = |
| 215 | Type.INTEGER.convertOptional(maxSplitO, "'split' argument of 'split'", null, -1); |
| 216 | List<String> result; |
| 217 | |
| 218 | try { |
| 219 | result = stringRSplit(self, sep, maxSplit); |
| 220 | } catch (IllegalArgumentException ex) { |
| 221 | throw new EvalException(loc, ex); |
| 222 | } |
| 223 | |
| 224 | return convert(result, env, loc); |
| 225 | } |
| 226 | }; |
Laurent Le Brun | 5482074 | 2015-07-30 16:43:52 +0000 | [diff] [blame] | 227 | |
Florian Weikert | 8e2e5fc | 2015-05-29 12:39:08 +0000 | [diff] [blame] | 228 | /** |
Laurent Le Brun | 5482074 | 2015-07-30 16:43:52 +0000 | [diff] [blame] | 229 | * Splits the given string into a list of words, using {@code separator} as a |
Florian Weikert | 8e2e5fc | 2015-05-29 12:39:08 +0000 | [diff] [blame] | 230 | * delimiter. |
Laurent Le Brun | 5482074 | 2015-07-30 16:43:52 +0000 | [diff] [blame] | 231 | * |
Florian Weikert | 8e2e5fc | 2015-05-29 12:39:08 +0000 | [diff] [blame] | 232 | * <p>At most {@code maxSplits} will be performed, going from right to left. |
| 233 | * |
| 234 | * @param input The input string. |
| 235 | * @param separator The separator string. |
| 236 | * @param maxSplits The maximum number of splits. Negative values mean unlimited splits. |
| 237 | * @return A list of words |
| 238 | * @throws IllegalArgumentException |
| 239 | */ |
| 240 | private static List<String> stringRSplit(String input, String separator, int maxSplits) |
| 241 | throws IllegalArgumentException { |
| 242 | if (separator.isEmpty()) { |
| 243 | throw new IllegalArgumentException("Empty separator"); |
| 244 | } |
| 245 | |
| 246 | if (maxSplits <= 0) { |
| 247 | maxSplits = Integer.MAX_VALUE; |
| 248 | } |
| 249 | |
| 250 | LinkedList<String> result = new LinkedList<>(); |
Florian Weikert | e741e8f | 2015-06-22 20:57:01 +0000 | [diff] [blame] | 251 | String[] parts = input.split(Pattern.quote(separator), -1); |
Florian Weikert | 8e2e5fc | 2015-05-29 12:39:08 +0000 | [diff] [blame] | 252 | int sepLen = separator.length(); |
Laurent Le Brun | 5482074 | 2015-07-30 16:43:52 +0000 | [diff] [blame] | 253 | int remainingLength = input.length(); |
Florian Weikert | 8e2e5fc | 2015-05-29 12:39:08 +0000 | [diff] [blame] | 254 | int splitsSoFar = 0; |
| 255 | |
Laurent Le Brun | 5482074 | 2015-07-30 16:43:52 +0000 | [diff] [blame] | 256 | // Copies parts from the array into the final list, starting at the end (because |
| 257 | // it's rsplit), as long as fewer than maxSplits splits are performed. The |
| 258 | // last spot in the list is reserved for the remaining string, whose length |
Florian Weikert | 8e2e5fc | 2015-05-29 12:39:08 +0000 | [diff] [blame] | 259 | // has to be tracked throughout the loop. |
| 260 | for (int pos = parts.length - 1; (pos >= 0) && (splitsSoFar < maxSplits); --pos) { |
| 261 | String current = parts[pos]; |
| 262 | result.addFirst(current); |
| 263 | |
| 264 | ++splitsSoFar; |
| 265 | remainingLength -= sepLen + current.length(); |
| 266 | } |
| 267 | |
| 268 | if (splitsSoFar == maxSplits && remainingLength >= 0) { |
Laurent Le Brun | 5482074 | 2015-07-30 16:43:52 +0000 | [diff] [blame] | 269 | result.addFirst(input.substring(0, remainingLength)); |
Florian Weikert | 8e2e5fc | 2015-05-29 12:39:08 +0000 | [diff] [blame] | 270 | } |
Laurent Le Brun | 5482074 | 2015-07-30 16:43:52 +0000 | [diff] [blame] | 271 | |
| 272 | return result; |
Florian Weikert | 8e2e5fc | 2015-05-29 12:39:08 +0000 | [diff] [blame] | 273 | } |
Laurent Le Brun | 5482074 | 2015-07-30 16:43:52 +0000 | [diff] [blame] | 274 | |
Googler | 7ad2f09 | 2015-05-28 11:04:47 +0000 | [diff] [blame] | 275 | @SkylarkSignature(name = "partition", objectType = StringModule.class, |
| 276 | returnType = HackHackEitherList.class, |
| 277 | doc = "Splits the input string at the first occurrence of the separator " |
| 278 | + "<code>sep</code> and returns the resulting partition as a three-element " |
| 279 | + "list of the form [substring_before, separator, substring_after].", |
| 280 | mandatoryPositionals = { |
| 281 | @Param(name = "self", type = String.class, doc = "This string.")}, |
| 282 | optionalPositionals = { |
| 283 | @Param(name = "sep", type = String.class, |
| 284 | defaultValue = "' '", doc = "The string to split on, default is space (\" \").")}, |
| 285 | useEnvironment = true, |
| 286 | useLocation = true) |
| 287 | private static BuiltinFunction partition = new BuiltinFunction("partition") { |
| 288 | @SuppressWarnings("unused") |
| 289 | public Object invoke(String self, String sep, Location loc, Environment env) |
| 290 | throws EvalException { |
Florian Weikert | 8e2e5fc | 2015-05-29 12:39:08 +0000 | [diff] [blame] | 291 | return partitionWrapper(self, sep, true, env, loc); |
Googler | 7ad2f09 | 2015-05-28 11:04:47 +0000 | [diff] [blame] | 292 | } |
| 293 | }; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 294 | |
Googler | 7ad2f09 | 2015-05-28 11:04:47 +0000 | [diff] [blame] | 295 | @SkylarkSignature(name = "rpartition", objectType = StringModule.class, |
| 296 | returnType = HackHackEitherList.class, |
| 297 | doc = "Splits the input string at the last occurrence of the separator " |
| 298 | + "<code>sep</code> and returns the resulting partition as a three-element " |
| 299 | + "list of the form [substring_before, separator, substring_after].", |
| 300 | mandatoryPositionals = { |
| 301 | @Param(name = "self", type = String.class, doc = "This string.")}, |
| 302 | optionalPositionals = { |
| 303 | @Param(name = "sep", type = String.class, |
| 304 | defaultValue = "' '", doc = "The string to split on, default is space (\" \").")}, |
| 305 | useEnvironment = true, |
| 306 | useLocation = true) |
| 307 | private static BuiltinFunction rpartition = new BuiltinFunction("rpartition") { |
| 308 | @SuppressWarnings("unused") |
| 309 | public Object invoke(String self, String sep, Location loc, Environment env) |
| 310 | throws EvalException { |
Florian Weikert | 8e2e5fc | 2015-05-29 12:39:08 +0000 | [diff] [blame] | 311 | return partitionWrapper(self, sep, false, env, loc); |
Googler | 7ad2f09 | 2015-05-28 11:04:47 +0000 | [diff] [blame] | 312 | } |
| 313 | }; |
| 314 | |
| 315 | /** |
Laurent Le Brun | 5482074 | 2015-07-30 16:43:52 +0000 | [diff] [blame] | 316 | * Wraps the stringPartition() method and converts its results and exceptions |
Googler | 7ad2f09 | 2015-05-28 11:04:47 +0000 | [diff] [blame] | 317 | * to the expected types. |
| 318 | * |
| 319 | * @param self The input string |
| 320 | * @param separator The string to split on |
Laurent Le Brun | 5482074 | 2015-07-30 16:43:52 +0000 | [diff] [blame] | 321 | * @param forward A flag that controls whether the input string is split around |
| 322 | * the first ({@code true}) or last ({@code false}) occurrence of the separator. |
Florian Weikert | 8e2e5fc | 2015-05-29 12:39:08 +0000 | [diff] [blame] | 323 | * @param env The current environment |
Googler | 7ad2f09 | 2015-05-28 11:04:47 +0000 | [diff] [blame] | 324 | * @param loc The location that is used for potential exceptions |
| 325 | * @return A list with three elements |
| 326 | */ |
| 327 | private static Object partitionWrapper(String self, String separator, boolean forward, |
Florian Weikert | 8e2e5fc | 2015-05-29 12:39:08 +0000 | [diff] [blame] | 328 | Environment env, Location loc) throws EvalException { |
Googler | 7ad2f09 | 2015-05-28 11:04:47 +0000 | [diff] [blame] | 329 | try { |
Florian Weikert | 8e2e5fc | 2015-05-29 12:39:08 +0000 | [diff] [blame] | 330 | return convert(stringPartition(self, separator, forward), env, loc); |
Googler | 7ad2f09 | 2015-05-28 11:04:47 +0000 | [diff] [blame] | 331 | } catch (IllegalArgumentException ex) { |
| 332 | throw new EvalException(loc, ex); |
| 333 | } |
| 334 | } |
| 335 | |
| 336 | /** |
| 337 | * Splits the input string at the {first|last} occurrence of the given separator |
Laurent Le Brun | 5482074 | 2015-07-30 16:43:52 +0000 | [diff] [blame] | 338 | * and returns the resulting partition as a three-tuple of Strings, contained |
Googler | 7ad2f09 | 2015-05-28 11:04:47 +0000 | [diff] [blame] | 339 | * in a {@code List}. |
Laurent Le Brun | 5482074 | 2015-07-30 16:43:52 +0000 | [diff] [blame] | 340 | * |
Googler | 7ad2f09 | 2015-05-28 11:04:47 +0000 | [diff] [blame] | 341 | * <p>If the input string does not contain the separator, the tuple will |
| 342 | * consist of the original input string and two empty strings. |
Laurent Le Brun | 5482074 | 2015-07-30 16:43:52 +0000 | [diff] [blame] | 343 | * |
| 344 | * <p>This method emulates the behavior of Python's str.partition() and |
Googler | 7ad2f09 | 2015-05-28 11:04:47 +0000 | [diff] [blame] | 345 | * str.rpartition(), depending on the value of the {@code forward} flag. |
Laurent Le Brun | 5482074 | 2015-07-30 16:43:52 +0000 | [diff] [blame] | 346 | * |
Googler | 7ad2f09 | 2015-05-28 11:04:47 +0000 | [diff] [blame] | 347 | * @param input The input string |
| 348 | * @param separator The string to split on |
Laurent Le Brun | 5482074 | 2015-07-30 16:43:52 +0000 | [diff] [blame] | 349 | * @param forward A flag that controls whether the input string is split around |
| 350 | * the first ({@code true}) or last ({@code false}) occurrence of the separator. |
| 351 | * @return A three-tuple (List) of the form [part_before_separator, separator, |
Googler | 7ad2f09 | 2015-05-28 11:04:47 +0000 | [diff] [blame] | 352 | * part_after_separator]. |
Laurent Le Brun | 5482074 | 2015-07-30 16:43:52 +0000 | [diff] [blame] | 353 | * |
Googler | 7ad2f09 | 2015-05-28 11:04:47 +0000 | [diff] [blame] | 354 | */ |
| 355 | private static List<String> stringPartition(String input, String separator, boolean forward) |
| 356 | throws IllegalArgumentException { |
| 357 | if (separator.isEmpty()) { |
| 358 | throw new IllegalArgumentException("Empty separator"); |
| 359 | } |
| 360 | |
| 361 | int partitionSize = 3; |
| 362 | ArrayList<String> result = new ArrayList<>(partitionSize); |
| 363 | int pos = forward ? input.indexOf(separator) : input.lastIndexOf(separator); |
| 364 | |
| 365 | if (pos < 0) { |
| 366 | for (int i = 0; i < partitionSize; ++i) { |
| 367 | result.add(""); |
| 368 | } |
| 369 | |
| 370 | // Following Python's implementation of str.partition() and str.rpartition(), |
| 371 | // the input string is copied to either the first or the last position in the |
| 372 | // list, depending on the value of the forward flag. |
| 373 | result.set(forward ? 0 : partitionSize - 1, input); |
| 374 | } else { |
| 375 | result.add(input.substring(0, pos)); |
| 376 | result.add(separator); |
| 377 | |
| 378 | // pos + sep.length() is at most equal to input.length(). This worst-case |
| 379 | // happens when the separator is at the end of the input string. However, |
| 380 | // substring() will return an empty string in this scenario, thus making |
| 381 | // any additional safety checks obsolete. |
| 382 | result.add(input.substring(pos + separator.length())); |
| 383 | } |
| 384 | |
| 385 | return result; |
| 386 | } |
Laurent Le Brun | 5482074 | 2015-07-30 16:43:52 +0000 | [diff] [blame] | 387 | |
Laurent Le Brun | b4114cc | 2015-08-26 14:53:37 +0000 | [diff] [blame] | 388 | @SkylarkSignature( |
| 389 | name = "capitalize", |
| 390 | objectType = StringModule.class, |
| 391 | returnType = String.class, |
| 392 | doc = |
| 393 | "Returns a copy of the string with its first character capitalized and the rest" |
| 394 | + "lowercased. This method does not support non-ascii characters.", |
| 395 | mandatoryPositionals = {@Param(name = "self", type = String.class, doc = "This string.")} |
| 396 | ) |
| 397 | private static BuiltinFunction capitalize = |
| 398 | new BuiltinFunction("capitalize") { |
| 399 | @SuppressWarnings("unused") |
| 400 | public String invoke(String self) throws EvalException { |
| 401 | if (self.isEmpty()) { |
| 402 | return self; |
| 403 | } |
| 404 | return Character.toUpperCase(self.charAt(0)) + self.substring(1).toLowerCase(); |
| 405 | } |
| 406 | }; |
| 407 | |
Florian Weikert | 006bf4f | 2015-08-03 12:28:35 +0000 | [diff] [blame] | 408 | @SkylarkSignature(name = "title", objectType = StringModule.class, |
| 409 | returnType = String.class, |
| 410 | doc = |
| 411 | "Converts the input string into title case, i.e. every word starts with an " |
| 412 | + "uppercase letter while the remaining letters are lowercase. In this " |
| 413 | + "context, a word means strictly a sequence of letters. This method does " |
| 414 | + "not support supplementary Unicode characters.", |
| 415 | mandatoryPositionals = { |
| 416 | @Param(name = "self", type = String.class, doc = "This string.")}) |
| 417 | private static BuiltinFunction title = new BuiltinFunction("title") { |
| 418 | @SuppressWarnings("unused") |
| 419 | public String invoke(String self) throws EvalException { |
| 420 | char[] data = self.toCharArray(); |
| 421 | boolean previousWasLetter = false; |
| 422 | |
| 423 | for (int pos = 0; pos < data.length; ++pos) { |
| 424 | char current = data[pos]; |
| 425 | boolean currentIsLetter = Character.isLetter(current); |
| 426 | |
| 427 | if (currentIsLetter) { |
| 428 | if (previousWasLetter && Character.isUpperCase(current)) { |
| 429 | data[pos] = Character.toLowerCase(current); |
| 430 | } else if (!previousWasLetter && Character.isLowerCase(current)) { |
| 431 | data[pos] = Character.toUpperCase(current); |
| 432 | } |
| 433 | } |
| 434 | previousWasLetter = currentIsLetter; |
| 435 | } |
| 436 | |
| 437 | return new String(data); |
| 438 | } |
| 439 | }; |
| 440 | |
Francois-Rene Rideau | 76023b9 | 2015-04-17 15:31:59 +0000 | [diff] [blame] | 441 | /** |
| 442 | * Common implementation for find, rfind, index, rindex. |
| 443 | * @param forward true if we want to return the last matching index. |
| 444 | */ |
| 445 | private static int stringFind(boolean forward, |
| 446 | String self, String sub, int start, Object end, String msg) |
| 447 | throws ConversionException { |
| 448 | String substr = pythonSubstring(self, start, end, msg); |
| 449 | int subpos = forward ? substr.indexOf(sub) : substr.lastIndexOf(sub); |
| 450 | start = clampIndex(start, self.length()); |
| 451 | return subpos < 0 ? subpos : subpos + start; |
| 452 | } |
| 453 | |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 454 | @SkylarkSignature(name = "rfind", objectType = StringModule.class, returnType = Integer.class, |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 455 | doc = "Returns the last index where <code>sub</code> is found, " |
| 456 | + "or -1 if no such index exists, optionally restricting to " |
| 457 | + "[<code>start</code>:<code>end</code>], " |
| 458 | + "<code>start</code> being inclusive and <code>end</code> being exclusive.", |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 459 | mandatoryPositionals = { |
| 460 | @Param(name = "self", type = String.class, doc = "This string."), |
| 461 | @Param(name = "sub", type = String.class, doc = "The substring to find.")}, |
| 462 | optionalPositionals = { |
| 463 | @Param(name = "start", type = Integer.class, defaultValue = "0", |
| 464 | doc = "Restrict to search from this position."), |
| 465 | @Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None", |
| 466 | doc = "optional position before which to restrict to search.")}) |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 467 | private static BuiltinFunction rfind = new BuiltinFunction("rfind") { |
| 468 | public Integer invoke(String self, String sub, Integer start, Object end) |
| 469 | throws ConversionException { |
| 470 | return stringFind(false, self, sub, start, end, "'end' argument to rfind"); |
| 471 | } |
| 472 | }; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 473 | |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 474 | @SkylarkSignature(name = "find", objectType = StringModule.class, returnType = Integer.class, |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 475 | doc = "Returns the first index where <code>sub</code> is found, " |
| 476 | + "or -1 if no such index exists, optionally restricting to " |
| 477 | + "[<code>start</code>:<code>end]</code>, " |
| 478 | + "<code>start</code> being inclusive and <code>end</code> being exclusive.", |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 479 | mandatoryPositionals = { |
| 480 | @Param(name = "self", type = String.class, doc = "This string."), |
| 481 | @Param(name = "sub", type = String.class, doc = "The substring to find.")}, |
| 482 | optionalPositionals = { |
| 483 | @Param(name = "start", type = Integer.class, defaultValue = "0", |
| 484 | doc = "Restrict to search from this position."), |
| 485 | @Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None", |
| 486 | doc = "optional position before which to restrict to search.")}) |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 487 | private static BuiltinFunction find = new BuiltinFunction("find") { |
| 488 | public Integer invoke(String self, String sub, Integer start, Object end) |
| 489 | throws ConversionException { |
| 490 | return stringFind(true, self, sub, start, end, "'end' argument to find"); |
| 491 | } |
| 492 | }; |
Laurent Le Brun | 4e116c7 | 2015-03-23 13:48:50 +0000 | [diff] [blame] | 493 | |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 494 | @SkylarkSignature(name = "rindex", objectType = StringModule.class, returnType = Integer.class, |
Laurent Le Brun | 4e116c7 | 2015-03-23 13:48:50 +0000 | [diff] [blame] | 495 | doc = "Returns the last index where <code>sub</code> is found, " |
Laurent Le Brun | 18d8499 | 2015-08-19 12:13:36 +0000 | [diff] [blame] | 496 | + "or raises an error if no such index exists, optionally restricting to " |
Laurent Le Brun | 4e116c7 | 2015-03-23 13:48:50 +0000 | [diff] [blame] | 497 | + "[<code>start</code>:<code>end</code>], " |
| 498 | + "<code>start</code> being inclusive and <code>end</code> being exclusive.", |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 499 | mandatoryPositionals = { |
| 500 | @Param(name = "self", type = String.class, doc = "This string."), |
| 501 | @Param(name = "sub", type = String.class, doc = "The substring to find.")}, |
| 502 | optionalPositionals = { |
| 503 | @Param(name = "start", type = Integer.class, defaultValue = "0", |
| 504 | doc = "Restrict to search from this position."), |
| 505 | @Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None", |
| 506 | doc = "optional position before which to restrict to search.")}, |
| 507 | useLocation = true) |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 508 | private static BuiltinFunction rindex = new BuiltinFunction("rindex") { |
| 509 | public Integer invoke(String self, String sub, Integer start, Object end, |
| 510 | Location loc) throws EvalException, ConversionException { |
| 511 | int res = stringFind(false, self, sub, start, end, "'end' argument to rindex"); |
| 512 | if (res < 0) { |
Francois-Rene Rideau | 8d5cce3 | 2015-06-16 23:12:04 +0000 | [diff] [blame] | 513 | throw new EvalException(loc, Printer.format("substring %r not found in %r", sub, self)); |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 514 | } |
| 515 | return res; |
| 516 | } |
| 517 | }; |
Laurent Le Brun | 4e116c7 | 2015-03-23 13:48:50 +0000 | [diff] [blame] | 518 | |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 519 | @SkylarkSignature(name = "index", objectType = StringModule.class, returnType = Integer.class, |
Laurent Le Brun | 4e116c7 | 2015-03-23 13:48:50 +0000 | [diff] [blame] | 520 | doc = "Returns the first index where <code>sub</code> is found, " |
Laurent Le Brun | 18d8499 | 2015-08-19 12:13:36 +0000 | [diff] [blame] | 521 | + "or raises an error if no such index exists, optionally restricting to " |
Laurent Le Brun | 4e116c7 | 2015-03-23 13:48:50 +0000 | [diff] [blame] | 522 | + "[<code>start</code>:<code>end]</code>, " |
| 523 | + "<code>start</code> being inclusive and <code>end</code> being exclusive.", |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 524 | mandatoryPositionals = { |
| 525 | @Param(name = "self", type = String.class, doc = "This string."), |
| 526 | @Param(name = "sub", type = String.class, doc = "The substring to find.")}, |
| 527 | optionalPositionals = { |
| 528 | @Param(name = "start", type = Integer.class, defaultValue = "0", |
| 529 | doc = "Restrict to search from this position."), |
| 530 | @Param(name = "end", type = Integer.class, noneable = true, |
| 531 | doc = "optional position before which to restrict to search.")}, |
| 532 | useLocation = true) |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 533 | private static BuiltinFunction index = new BuiltinFunction("index") { |
| 534 | public Integer invoke(String self, String sub, Integer start, Object end, |
| 535 | Location loc) throws EvalException, ConversionException { |
| 536 | int res = stringFind(true, self, sub, start, end, "'end' argument to index"); |
| 537 | if (res < 0) { |
Francois-Rene Rideau | 8d5cce3 | 2015-06-16 23:12:04 +0000 | [diff] [blame] | 538 | throw new EvalException(loc, Printer.format("substring %r not found in %r", sub, self)); |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 539 | } |
| 540 | return res; |
| 541 | } |
| 542 | }; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 543 | |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 544 | @SkylarkSignature(name = "count", objectType = StringModule.class, returnType = Integer.class, |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 545 | doc = "Returns the number of (non-overlapping) occurrences of substring <code>sub</code> in " |
| 546 | + "string, optionally restricting to [<code>start</code>:<code>end</code>], " |
| 547 | + "<code>start</code> being inclusive and <code>end</code> being exclusive.", |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 548 | mandatoryPositionals = { |
| 549 | @Param(name = "self", type = String.class, doc = "This string."), |
| 550 | @Param(name = "sub", type = String.class, doc = "The substring to count.")}, |
| 551 | optionalPositionals = { |
| 552 | @Param(name = "start", type = Integer.class, defaultValue = "0", |
| 553 | doc = "Restrict to search from this position."), |
| 554 | @Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None", |
| 555 | doc = "optional position before which to restrict to search.")}) |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 556 | private static BuiltinFunction count = new BuiltinFunction("count") { |
| 557 | public Integer invoke(String self, String sub, Integer start, Object end) |
| 558 | throws ConversionException { |
| 559 | String str = pythonSubstring(self, start, end, "'end' operand of 'find'"); |
| 560 | if (sub.isEmpty()) { |
| 561 | return str.length() + 1; |
| 562 | } |
| 563 | int count = 0; |
| 564 | int index = -1; |
| 565 | while ((index = str.indexOf(sub)) >= 0) { |
| 566 | count++; |
| 567 | str = str.substring(index + sub.length()); |
| 568 | } |
| 569 | return count; |
| 570 | } |
| 571 | }; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 572 | |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 573 | @SkylarkSignature(name = "endswith", objectType = StringModule.class, returnType = Boolean.class, |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 574 | doc = "Returns True if the string ends with <code>sub</code>, " |
| 575 | + "otherwise False, optionally restricting to [<code>start</code>:<code>end</code>], " |
| 576 | + "<code>start</code> being inclusive and <code>end</code> being exclusive.", |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 577 | mandatoryPositionals = { |
| 578 | @Param(name = "self", type = String.class, doc = "This string."), |
| 579 | @Param(name = "sub", type = String.class, doc = "The substring to check.")}, |
| 580 | optionalPositionals = { |
| 581 | @Param(name = "start", type = Integer.class, defaultValue = "0", |
| 582 | doc = "Test beginning at this position."), |
| 583 | @Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None", |
| 584 | doc = "optional position at which to stop comparing.")}) |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 585 | private static BuiltinFunction endswith = new BuiltinFunction( |
Francois-Rene Rideau | 0f7ba34 | 2015-08-31 16:16:21 +0000 | [diff] [blame^] | 586 | "endswith", SkylarkList.tuple(0, Runtime.NONE)) { |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 587 | public Boolean invoke(String self, String sub, Integer start, Object end) |
| 588 | throws ConversionException { |
| 589 | return pythonSubstring(self, start, end, "'end' operand of 'endswith'").endsWith(sub); |
| 590 | } |
| 591 | }; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 592 | |
Laurent Le Brun | aa83c3a | 2015-05-08 15:00:35 +0000 | [diff] [blame] | 593 | // In Python, formatting is very complex. |
| 594 | // We handle here the simplest case which provides most of the value of the function. |
| 595 | // https://docs.python.org/3/library/string.html#formatstrings |
| 596 | @SkylarkSignature(name = "format", objectType = StringModule.class, returnType = String.class, |
Laurent Le Brun | 18d8499 | 2015-08-19 12:13:36 +0000 | [diff] [blame] | 597 | doc = "Perform string interpolation. Format strings contain replacement fields " |
| 598 | + "surrounded by curly braces <code>{}</code>. Anything that is not contained " |
| 599 | + "in braces is considered literal text, which is copied unchanged to the output." |
| 600 | + "If you need to include a brace character in the literal text, it can be " |
| 601 | + "escaped by doubling: <code>{{</code> and <code>}}</code>" |
| 602 | + "A replacement field can be either a name, a number, or empty. Values are " |
| 603 | + "converted to strings using the <a href=\"globals.html#str\">str</a> function." |
Laurent Le Brun | aa83c3a | 2015-05-08 15:00:35 +0000 | [diff] [blame] | 604 | + "<pre class=\"language-python\">" |
Laurent Le Brun | 18d8499 | 2015-08-19 12:13:36 +0000 | [diff] [blame] | 605 | + "# Access in order:\n" |
| 606 | + "\"{} < {}\".format(4, 5) == \"4 < 5\"\n" |
| 607 | + "# Access by position:\n" |
| 608 | + "\"{1}, {0}\".format(2, 1) == \"1, 2\"\n" |
| 609 | + "# Access by name:\n" |
Laurent Le Brun | aa83c3a | 2015-05-08 15:00:35 +0000 | [diff] [blame] | 610 | + "\"x{key}x\".format(key = 2) == \"x2x\"</pre>\n", |
| 611 | mandatoryPositionals = { |
Florian Weikert | 8a84dd9 | 2015-06-26 15:39:57 +0000 | [diff] [blame] | 612 | @Param(name = "self", type = String.class, doc = "This string."), |
Laurent Le Brun | aa83c3a | 2015-05-08 15:00:35 +0000 | [diff] [blame] | 613 | }, |
Florian Weikert | 8a84dd9 | 2015-06-26 15:39:57 +0000 | [diff] [blame] | 614 | extraPositionals = { |
| 615 | @Param(name = "args", type = HackHackEitherList.class, defaultValue = "[]", |
| 616 | doc = "List of arguments"), |
| 617 | }, |
| 618 | extraKeywords = {@Param(name = "kwargs", doc = "Dictionary of arguments")}, |
Laurent Le Brun | aa83c3a | 2015-05-08 15:00:35 +0000 | [diff] [blame] | 619 | useLocation = true) |
| 620 | private static BuiltinFunction format = new BuiltinFunction("format") { |
Florian Weikert | 8a84dd9 | 2015-06-26 15:39:57 +0000 | [diff] [blame] | 621 | @SuppressWarnings("unused") |
| 622 | public String invoke(String self, Object args, Map<String, Object> kwargs, Location loc) |
| 623 | throws ConversionException, EvalException { |
| 624 | return new FormatParser(loc).format(self, Type.OBJECT_LIST.convert(args, "format"), kwargs); |
Laurent Le Brun | aa83c3a | 2015-05-08 15:00:35 +0000 | [diff] [blame] | 625 | } |
| 626 | }; |
| 627 | |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 628 | @SkylarkSignature(name = "startswith", objectType = StringModule.class, |
| 629 | returnType = Boolean.class, |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 630 | doc = "Returns True if the string starts with <code>sub</code>, " |
| 631 | + "otherwise False, optionally restricting to [<code>start</code>:<code>end</code>], " |
| 632 | + "<code>start</code> being inclusive and <code>end</code> being exclusive.", |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 633 | mandatoryPositionals = { |
| 634 | @Param(name = "self", type = String.class, doc = "This string."), |
| 635 | @Param(name = "sub", type = String.class, doc = "The substring to check.")}, |
| 636 | optionalPositionals = { |
| 637 | @Param(name = "start", type = Integer.class, defaultValue = "0", |
| 638 | doc = "Test beginning at this position."), |
| 639 | @Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None", |
| 640 | doc = "Stop comparing at this position.")}) |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 641 | private static BuiltinFunction startswith = new BuiltinFunction("startswith") { |
| 642 | public Boolean invoke(String self, String sub, Integer start, Object end) |
| 643 | throws ConversionException { |
| 644 | return pythonSubstring(self, start, end, "'end' operand of 'startswith'").startsWith(sub); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 645 | } |
| 646 | }; |
| 647 | |
| 648 | // TODO(bazel-team): Maybe support an argument to tell the type of the whitespace. |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 649 | @SkylarkSignature(name = "strip", objectType = StringModule.class, returnType = String.class, |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 650 | doc = "Returns a copy of the string in which all whitespace characters " |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 651 | + "have been stripped from the beginning and the end of the string.", |
| 652 | mandatoryPositionals = { |
| 653 | @Param(name = "self", type = String.class, doc = "This string.")}) |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 654 | private static BuiltinFunction strip = new BuiltinFunction("strip") { |
| 655 | public String invoke(String self) { |
| 656 | return self.trim(); |
| 657 | } |
| 658 | }; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 659 | |
Laurent Le Brun | eeef30f | 2015-03-16 15:12:35 +0000 | [diff] [blame] | 660 | // slice operator |
Francois-Rene Rideau | 0f7ba34 | 2015-08-31 16:16:21 +0000 | [diff] [blame^] | 661 | @SkylarkSignature(name = "$slice", objectType = String.class, |
| 662 | documented = false, |
| 663 | mandatoryPositionals = { |
| 664 | @Param(name = "self", type = String.class, doc = "This string."), |
| 665 | @Param(name = "start", type = Integer.class, doc = "start position of the slice."), |
| 666 | @Param(name = "end", type = Integer.class, doc = "end position of the slice.")}, |
| 667 | doc = "x[<code>start</code>:<code>end</code>] returns a slice or a list slice.") |
| 668 | private static BuiltinFunction stringSlice = new BuiltinFunction("$slice") { |
| 669 | public Object invoke(String self, Integer left, Integer right) |
| 670 | throws EvalException, ConversionException { |
| 671 | return pythonSubstring(self, left, right, ""); |
| 672 | } |
| 673 | }; |
Laurent Le Brun | eeef30f | 2015-03-16 15:12:35 +0000 | [diff] [blame] | 674 | |
Francois-Rene Rideau | 0f7ba34 | 2015-08-31 16:16:21 +0000 | [diff] [blame^] | 675 | @SkylarkSignature(name = "$slice", objectType = List.class, |
| 676 | documented = false, |
| 677 | mandatoryPositionals = { |
| 678 | @Param(name = "self", type = List.class, doc = "This list or tuple."), |
| 679 | @Param(name = "start", type = Integer.class, doc = "start position of the slice."), |
| 680 | @Param(name = "end", type = Integer.class, doc = "end position of the slice.")}, |
| 681 | doc = "x[<code>start</code>:<code>end</code>] returns a slice or a list slice.") |
| 682 | private static BuiltinFunction listSlice = new BuiltinFunction("$slice") { |
| 683 | public Object invoke(List<Object> self, Integer left, Integer right) |
| 684 | throws EvalException, ConversionException { |
| 685 | return sliceList(self, left, right); |
| 686 | } |
| 687 | }; |
| 688 | |
| 689 | @SkylarkSignature(name = "$slice", objectType = SkylarkList.class, |
| 690 | documented = false, |
| 691 | mandatoryPositionals = { |
| 692 | @Param(name = "self", type = SkylarkList.class, doc = "This list or tuple."), |
| 693 | @Param(name = "start", type = Integer.class, doc = "start position of the slice."), |
| 694 | @Param(name = "end", type = Integer.class, doc = "end position of the slice.")}, |
| 695 | doc = "x[<code>start</code>:<code>end</code>] returns a slice or a list slice.", |
| 696 | useLocation = true) |
| 697 | private static BuiltinFunction skylarkListSlice = new BuiltinFunction("$slice") { |
| 698 | public Object invoke(SkylarkList self, Integer left, Integer right, |
| 699 | Location loc) throws EvalException, ConversionException { |
| 700 | return SkylarkList.list(sliceList(self.toList(), left, right), loc); |
| 701 | } |
| 702 | }; |
| 703 | |
| 704 | private static List<Object> sliceList(List<Object> list, Integer left, Integer right) { |
| 705 | left = clampIndex(left, list.size()); |
| 706 | right = clampIndex(right, list.size()); |
| 707 | if (left > right) { |
| 708 | left = right; |
| 709 | } |
| 710 | return list.subList(left, right); |
| 711 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 712 | |
| 713 | // supported list methods |
Laurent Le Brun | 6a4d36a | 2015-08-21 10:57:41 +0000 | [diff] [blame] | 714 | @SkylarkSignature( |
| 715 | name = "sorted", |
| 716 | returnType = HackHackEitherList.class, |
| 717 | doc = |
| 718 | "Sort a collection. Elements are sorted first by their type, " |
| 719 | + "then by their value (in ascending order).", |
| 720 | mandatoryPositionals = {@Param(name = "self", type = Object.class, doc = "This collection.")}, |
| 721 | useLocation = true, |
| 722 | useEnvironment = true |
| 723 | ) |
| 724 | private static BuiltinFunction sorted = |
| 725 | new BuiltinFunction("sorted") { |
| 726 | public Object invoke(Object self, Location loc, Environment env) |
| 727 | throws EvalException, ConversionException { |
| 728 | try { |
| 729 | Collection<?> col = |
| 730 | Ordering.from(EvalUtils.SKYLARK_COMPARATOR) |
| 731 | .sortedCopy(EvalUtils.toCollection(self, loc)); |
| 732 | return convert(col, env, loc); |
| 733 | } catch (EvalUtils.ComparisonException e) { |
| 734 | throw new EvalException(loc, e); |
| 735 | } |
| 736 | } |
| 737 | }; |
Laurent Le Brun | ef69ec5 | 2015-04-16 18:58:34 +0000 | [diff] [blame] | 738 | |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 739 | // This function has a SkylarkSignature but is only used by the Build language, not Skylark. |
Laurent Le Brun | 6a4d36a | 2015-08-21 10:57:41 +0000 | [diff] [blame] | 740 | @SkylarkSignature( |
| 741 | name = "append", |
Francois-Rene Rideau | 0f7ba34 | 2015-08-31 16:16:21 +0000 | [diff] [blame^] | 742 | objectType = List.class, |
| 743 | returnType = Runtime.NoneType.class, |
Laurent Le Brun | 6a4d36a | 2015-08-21 10:57:41 +0000 | [diff] [blame] | 744 | documented = false, |
| 745 | doc = "Adds an item to the end of the list.", |
| 746 | mandatoryPositionals = { |
| 747 | // we use List rather than SkylarkList because this is actually for use *outside* Skylark |
| 748 | @Param(name = "self", type = List.class, doc = "This list."), |
| 749 | @Param(name = "item", type = Object.class, doc = "Item to add at the end.") |
| 750 | }, |
| 751 | useLocation = true, |
Francois-Rene Rideau | 0f7ba34 | 2015-08-31 16:16:21 +0000 | [diff] [blame^] | 752 | useEnvironment = true) |
Laurent Le Brun | 6a4d36a | 2015-08-21 10:57:41 +0000 | [diff] [blame] | 753 | private static BuiltinFunction append = |
| 754 | new BuiltinFunction("append") { |
Francois-Rene Rideau | 0f7ba34 | 2015-08-31 16:16:21 +0000 | [diff] [blame^] | 755 | public Runtime.NoneType invoke(List<Object> self, Object item, |
| 756 | Location loc, Environment env) throws EvalException, ConversionException { |
| 757 | if (env.isSkylark()) { |
| 758 | throw new EvalException(loc, |
| 759 | "function 'append' is not available in Skylark"); |
| 760 | } |
| 761 | if (EvalUtils.isTuple(self)) { |
| 762 | throw new EvalException(loc, |
| 763 | "function 'append' is not defined on object of type 'Tuple'"); |
| 764 | } |
Laurent Le Brun | 6a4d36a | 2015-08-21 10:57:41 +0000 | [diff] [blame] | 765 | self.add(item); |
Francois-Rene Rideau | 0f7ba34 | 2015-08-31 16:16:21 +0000 | [diff] [blame^] | 766 | return Runtime.NONE; |
Laurent Le Brun | 6a4d36a | 2015-08-21 10:57:41 +0000 | [diff] [blame] | 767 | } |
| 768 | }; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 769 | |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 770 | // This function has a SkylarkSignature but is only used by the Build language, not Skylark. |
Francois-Rene Rideau | 0f7ba34 | 2015-08-31 16:16:21 +0000 | [diff] [blame^] | 771 | @SkylarkSignature( |
| 772 | name = "extend", |
| 773 | objectType = List.class, |
| 774 | returnType = Runtime.NoneType.class, |
| 775 | documented = false, |
| 776 | doc = "Adds all items to the end of the list.", |
| 777 | mandatoryPositionals = { |
| 778 | // we use List rather than SkylarkList because this is actually for use *outside* Skylark |
| 779 | @Param(name = "self", type = List.class, doc = "This list."), |
| 780 | @Param(name = "items", type = List.class, doc = "Items to add at the end.")}, |
| 781 | useLocation = true, |
| 782 | useEnvironment = true) |
| 783 | private static BuiltinFunction extend = |
| 784 | new BuiltinFunction("extend") { |
| 785 | public Runtime.NoneType invoke(List<Object> self, List<Object> items, |
| 786 | Location loc, Environment env) throws EvalException, ConversionException { |
| 787 | if (env.isSkylark()) { |
| 788 | throw new EvalException(loc, |
| 789 | "function 'append' is not available in Skylark"); |
| 790 | } |
| 791 | if (EvalUtils.isTuple(self)) { |
| 792 | throw new EvalException(loc, |
| 793 | "function 'extend' is not defined on object of type 'Tuple'"); |
| 794 | } |
| 795 | self.addAll(items); |
| 796 | return Runtime.NONE; |
| 797 | } |
| 798 | }; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 799 | |
| 800 | // dictionary access operator |
Francois-Rene Rideau | 0f7ba34 | 2015-08-31 16:16:21 +0000 | [diff] [blame^] | 801 | @SkylarkSignature(name = "$index", documented = false, objectType = Map.class, |
| 802 | doc = "Looks up a value in a dictionary.", |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 803 | mandatoryPositionals = { |
Francois-Rene Rideau | 0f7ba34 | 2015-08-31 16:16:21 +0000 | [diff] [blame^] | 804 | @Param(name = "self", type = Map.class, doc = "This object."), |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 805 | @Param(name = "key", type = Object.class, doc = "The index or key to access.")}, |
| 806 | useLocation = true) |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 807 | private static BuiltinFunction indexOperator = new BuiltinFunction("$index") { |
Francois-Rene Rideau | 0f7ba34 | 2015-08-31 16:16:21 +0000 | [diff] [blame^] | 808 | public Object invoke(Map<?, ?> self, Object key, |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 809 | Location loc) throws EvalException, ConversionException { |
Francois-Rene Rideau | 0f7ba34 | 2015-08-31 16:16:21 +0000 | [diff] [blame^] | 810 | if (!self.containsKey(key)) { |
| 811 | throw new EvalException(loc, Printer.format("Key %r not found in dictionary", key)); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 812 | } |
Francois-Rene Rideau | 0f7ba34 | 2015-08-31 16:16:21 +0000 | [diff] [blame^] | 813 | return self.get(key); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 814 | } |
| 815 | }; |
| 816 | |
Francois-Rene Rideau | 0f7ba34 | 2015-08-31 16:16:21 +0000 | [diff] [blame^] | 817 | // list access operator |
| 818 | @SkylarkSignature(name = "$index", documented = false, objectType = SkylarkList.class, |
| 819 | doc = "Returns the nth element of a list.", |
| 820 | mandatoryPositionals = { |
| 821 | @Param(name = "self", type = SkylarkList.class, doc = "This object."), |
| 822 | @Param(name = "key", type = Object.class, doc = "The index or key to access.")}, |
| 823 | useLocation = true) |
| 824 | private static BuiltinFunction skylarkListIndexOperator = new BuiltinFunction("$index") { |
| 825 | public Object invoke(SkylarkList self, Object key, |
| 826 | Location loc) throws EvalException, ConversionException { |
| 827 | if (self.isEmpty()) { |
| 828 | throw new EvalException(loc, "List is empty"); |
| 829 | } |
| 830 | int index = getListIndex(key, self.size(), loc); |
| 831 | return self.get(index); |
| 832 | } |
| 833 | }; |
| 834 | |
| 835 | |
| 836 | // list access operator |
| 837 | @SkylarkSignature(name = "$index", documented = false, objectType = List.class, |
| 838 | doc = "Returns the nth element of a list.", |
| 839 | mandatoryPositionals = { |
| 840 | @Param(name = "self", type = List.class, doc = "This object."), |
| 841 | @Param(name = "key", type = Object.class, doc = "The index or key to access.")}, |
| 842 | useLocation = true) |
| 843 | private static BuiltinFunction listIndexOperator = new BuiltinFunction("$index") { |
| 844 | public Object invoke(List<?> self, Object key, |
| 845 | Location loc) throws EvalException, ConversionException { |
| 846 | if (self.isEmpty()) { |
| 847 | throw new EvalException(loc, "List is empty"); |
| 848 | } |
| 849 | int index = getListIndex(key, self.size(), loc); |
| 850 | return self.get(index); |
| 851 | } |
| 852 | }; |
| 853 | |
| 854 | @SkylarkSignature(name = "$index", documented = false, objectType = String.class, |
| 855 | doc = "Returns the nth element of a string.", |
| 856 | mandatoryPositionals = { |
| 857 | @Param(name = "self", type = String.class, doc = "This string."), |
| 858 | @Param(name = "key", type = Object.class, doc = "The index or key to access.")}, |
| 859 | useLocation = true) |
| 860 | private static BuiltinFunction stringIndexOperator = new BuiltinFunction("$index") { |
| 861 | public Object invoke(String self, Object key, |
| 862 | Location loc) throws EvalException, ConversionException { |
| 863 | int index = getListIndex(key, self.length(), loc); |
| 864 | return self.substring(index, index + 1); |
| 865 | } |
| 866 | }; |
| 867 | |
| 868 | @SkylarkSignature(name = "values", objectType = Map.class, |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 869 | returnType = HackHackEitherList.class, |
Laurent Le Brun | 9d27a01 | 2015-03-31 12:28:02 +0000 | [diff] [blame] | 870 | doc = "Return the list of values. Dictionaries are always sorted by their keys:" |
| 871 | + "<pre class=\"language-python\">" |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 872 | + "{2: \"a\", 4: \"b\", 1: \"c\"}.values() == [\"c\", \"a\", \"b\"]</pre>\n", |
| 873 | mandatoryPositionals = {@Param(name = "self", type = Map.class, doc = "This dict.")}, |
| 874 | useLocation = true, useEnvironment = true) |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 875 | private static BuiltinFunction values = new BuiltinFunction("values") { |
| 876 | public Object invoke(Map<?, ?> self, |
| 877 | Location loc, Environment env) throws EvalException, ConversionException { |
Laurent Le Brun | 196c1a7 | 2015-03-18 13:03:04 +0000 | [diff] [blame] | 878 | // Use a TreeMap to ensure consistent ordering. |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 879 | Map<?, ?> dict = new TreeMap<>(self); |
| 880 | return convert(dict.values(), env, loc); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 881 | } |
| 882 | }; |
| 883 | |
Francois-Rene Rideau | 0f7ba34 | 2015-08-31 16:16:21 +0000 | [diff] [blame^] | 884 | @SkylarkSignature(name = "items", objectType = Map.class, |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 885 | returnType = HackHackEitherList.class, |
Laurent Le Brun | 9d27a01 | 2015-03-31 12:28:02 +0000 | [diff] [blame] | 886 | doc = "Return the list of key-value tuples. Dictionaries are always sorted by their keys:" |
| 887 | + "<pre class=\"language-python\">" |
| 888 | + "{2: \"a\", 4: \"b\", 1: \"c\"}.items() == [(1, \"c\"), (2, \"a\"), (4, \"b\")]" |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 889 | + "</pre>\n", |
| 890 | mandatoryPositionals = { |
| 891 | @Param(name = "self", type = Map.class, doc = "This dict.")}, |
| 892 | useLocation = true, useEnvironment = true) |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 893 | private static BuiltinFunction items = new BuiltinFunction("items") { |
| 894 | public Object invoke(Map<?, ?> self, |
| 895 | Location loc, Environment env) throws EvalException, ConversionException { |
Laurent Le Brun | 196c1a7 | 2015-03-18 13:03:04 +0000 | [diff] [blame] | 896 | // Use a TreeMap to ensure consistent ordering. |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 897 | Map<?, ?> dict = new TreeMap<>(self); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 898 | List<Object> list = Lists.newArrayListWithCapacity(dict.size()); |
| 899 | for (Map.Entry<?, ?> entries : dict.entrySet()) { |
| 900 | List<?> item = ImmutableList.of(entries.getKey(), entries.getValue()); |
Francois-Rene Rideau | 22aab6d | 2015-08-25 22:55:31 +0000 | [diff] [blame] | 901 | list.add(env.isSkylark() ? SkylarkList.tuple(item) : item); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 902 | } |
Francois-Rene Rideau | 0f7ba34 | 2015-08-31 16:16:21 +0000 | [diff] [blame^] | 903 | return convert(list, env, loc); |
| 904 | } |
| 905 | }; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 906 | |
Francois-Rene Rideau | 0f7ba34 | 2015-08-31 16:16:21 +0000 | [diff] [blame^] | 907 | @SkylarkSignature(name = "keys", objectType = Map.class, |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 908 | returnType = HackHackEitherList.class, |
Laurent Le Brun | 9d27a01 | 2015-03-31 12:28:02 +0000 | [diff] [blame] | 909 | doc = "Return the list of keys. Dictionaries are always sorted by their keys:" |
| 910 | + "<pre class=\"language-python\">{2: \"a\", 4: \"b\", 1: \"c\"}.keys() == [1, 2, 4]" |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 911 | + "</pre>\n", |
| 912 | mandatoryPositionals = { |
| 913 | @Param(name = "self", type = Map.class, doc = "This dict.")}, |
| 914 | useLocation = true, useEnvironment = true) |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 915 | // Skylark will only call this on a dict; and |
| 916 | // allowed keys are all Comparable... if not mutually, it's OK to get a runtime exception. |
| 917 | private static BuiltinFunction keys = new BuiltinFunction("keys") { |
| 918 | public Object invoke(Map<Comparable<?>, ?> dict, |
| 919 | Location loc, Environment env) throws EvalException { |
| 920 | return convert(Ordering.natural().sortedCopy(dict.keySet()), env, loc); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 921 | } |
| 922 | }; |
| 923 | |
Francois-Rene Rideau | 0f7ba34 | 2015-08-31 16:16:21 +0000 | [diff] [blame^] | 924 | @SkylarkSignature(name = "get", objectType = Map.class, |
Laurent Le Brun | f914897 | 2015-03-31 13:54:36 +0000 | [diff] [blame] | 925 | doc = "Return the value for <code>key</code> if <code>key</code> is in the dictionary, " |
| 926 | + "else <code>default</code>. If <code>default</code> is not given, it defaults to " |
| 927 | + "<code>None</code>, so that this method never throws an error.", |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 928 | mandatoryPositionals = { |
| 929 | @Param(name = "self", doc = "This dict."), |
Laurent Le Brun | f914897 | 2015-03-31 13:54:36 +0000 | [diff] [blame] | 930 | @Param(name = "key", doc = "The key to look for.")}, |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 931 | optionalPositionals = { |
| 932 | @Param(name = "default", defaultValue = "None", |
Laurent Le Brun | f914897 | 2015-03-31 13:54:36 +0000 | [diff] [blame] | 933 | doc = "The default value to use (instead of None) if the key is not found.")}) |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 934 | private static BuiltinFunction get = new BuiltinFunction("get") { |
| 935 | public Object invoke(Map<?, ?> self, Object key, Object defaultValue) { |
| 936 | if (self.containsKey(key)) { |
| 937 | return self.get(key); |
Laurent Le Brun | f914897 | 2015-03-31 13:54:36 +0000 | [diff] [blame] | 938 | } |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 939 | return defaultValue; |
Laurent Le Brun | f914897 | 2015-03-31 13:54:36 +0000 | [diff] [blame] | 940 | } |
| 941 | }; |
| 942 | |
Laurent Le Brun | eeef30f | 2015-03-16 15:12:35 +0000 | [diff] [blame] | 943 | // TODO(bazel-team): Use the same type for both Skylark and BUILD files. |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 944 | @SuppressWarnings("unchecked") |
| 945 | private static Iterable<Object> convert(Collection<?> list, Environment env, Location loc) |
| 946 | throws EvalException { |
Francois-Rene Rideau | 22aab6d | 2015-08-25 22:55:31 +0000 | [diff] [blame] | 947 | if (env.isSkylark()) { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 948 | return SkylarkList.list(list, loc); |
| 949 | } else { |
| 950 | return Lists.newArrayList(list); |
| 951 | } |
| 952 | } |
| 953 | |
| 954 | // unary minus |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 955 | @SkylarkSignature(name = "-", returnType = Integer.class, |
Francois-Rene Rideau | 0f7ba34 | 2015-08-31 16:16:21 +0000 | [diff] [blame^] | 956 | documented = false, |
| 957 | doc = "Unary minus operator.", |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 958 | mandatoryPositionals = { |
| 959 | @Param(name = "num", type = Integer.class, doc = "The number to negate.")}) |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 960 | private static BuiltinFunction minus = new BuiltinFunction("-") { |
| 961 | public Integer invoke(Integer num) throws ConversionException { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 962 | return -num; |
| 963 | } |
| 964 | }; |
| 965 | |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 966 | @SkylarkSignature(name = "list", returnType = SkylarkList.class, |
Laurent Le Brun | 9d27a01 | 2015-03-31 12:28:02 +0000 | [diff] [blame] | 967 | doc = "Converts a collection (e.g. set or dictionary) to a list." |
| 968 | + "<pre class=\"language-python\">list([1, 2]) == [1, 2]\n" |
| 969 | + "list(set([2, 3, 2])) == [2, 3]\n" |
| 970 | + "list({5: \"a\", 2: \"b\", 4: \"c\"}) == [2, 4, 5]</pre>", |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 971 | mandatoryPositionals = {@Param(name = "x", doc = "The object to convert.")}, |
| 972 | useLocation = true) |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 973 | private static BuiltinFunction list = new BuiltinFunction("list") { |
| 974 | public SkylarkList invoke(Object x, Location loc) throws EvalException { |
| 975 | return SkylarkList.list(EvalUtils.toCollection(x, loc), loc); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 976 | } |
| 977 | }; |
| 978 | |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 979 | @SkylarkSignature(name = "len", returnType = Integer.class, doc = |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 980 | "Returns the length of a string, list, tuple, set, or dictionary.", |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 981 | mandatoryPositionals = {@Param(name = "x", doc = "The object to check length of.")}, |
| 982 | useLocation = true) |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 983 | private static BuiltinFunction len = new BuiltinFunction("len") { |
| 984 | public Integer invoke(Object x, Location loc) throws EvalException { |
| 985 | int l = EvalUtils.size(x); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 986 | if (l == -1) { |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 987 | throw new EvalException(loc, EvalUtils.getDataTypeName(x) + " is not iterable"); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 988 | } |
| 989 | return l; |
| 990 | } |
| 991 | }; |
| 992 | |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 993 | @SkylarkSignature(name = "str", returnType = String.class, doc = |
Laurent Le Brun | faf7841 | 2015-07-28 16:13:00 +0000 | [diff] [blame] | 994 | "Converts any object to string. This is useful for debugging." |
| 995 | + "<pre class=\"language-python\">str(\"ab\") == \"ab\"</pre>", |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 996 | mandatoryPositionals = {@Param(name = "x", doc = "The object to convert.")}) |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 997 | private static BuiltinFunction str = new BuiltinFunction("str") { |
Francois-Rene Rideau | d61f531 | 2015-06-13 03:34:47 +0000 | [diff] [blame] | 998 | public String invoke(Object x) { |
| 999 | return Printer.str(x); |
| 1000 | } |
| 1001 | }; |
| 1002 | |
| 1003 | @SkylarkSignature(name = "repr", returnType = String.class, doc = |
Laurent Le Brun | faf7841 | 2015-07-28 16:13:00 +0000 | [diff] [blame] | 1004 | "Converts any object to a string representation. This is useful for debugging.<br>" |
| 1005 | + "<pre class=\"language-python\">str(\"ab\") == \\\"ab\\\"</pre>", |
Francois-Rene Rideau | d61f531 | 2015-06-13 03:34:47 +0000 | [diff] [blame] | 1006 | mandatoryPositionals = {@Param(name = "x", doc = "The object to convert.")}) |
| 1007 | private static BuiltinFunction repr = new BuiltinFunction("repr") { |
| 1008 | public String invoke(Object x) { |
| 1009 | return Printer.repr(x); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1010 | } |
| 1011 | }; |
| 1012 | |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 1013 | @SkylarkSignature(name = "bool", returnType = Boolean.class, |
Laurent Le Brun | faf7841 | 2015-07-28 16:13:00 +0000 | [diff] [blame] | 1014 | doc = "Constructor for the bool type. " |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1015 | + "It returns False if the object is None, False, an empty string, the number 0, or an " |
Laurent Le Brun | faf7841 | 2015-07-28 16:13:00 +0000 | [diff] [blame] | 1016 | + "empty collection. Otherwise, it returns True.", |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 1017 | mandatoryPositionals = {@Param(name = "x", doc = "The variable to convert.")}) |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 1018 | private static BuiltinFunction bool = new BuiltinFunction("bool") { |
| 1019 | public Boolean invoke(Object x) throws EvalException { |
| 1020 | return EvalUtils.toBoolean(x); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1021 | } |
| 1022 | }; |
| 1023 | |
Laurent Le Brun | f4648de | 2015-05-07 14:00:32 +0000 | [diff] [blame] | 1024 | @SkylarkSignature(name = "int", returnType = Integer.class, doc = "Converts a value to int. " |
| 1025 | + "If the argument is a string, it is converted using base 10 and raises an error if the " |
| 1026 | + "conversion fails. If the argument is a bool, it returns 0 (False) or 1 (True). " |
| 1027 | + "If the argument is an int, it is simply returned." |
Laurent Le Brun | 0c44aa4 | 2015-04-02 11:32:47 +0000 | [diff] [blame] | 1028 | + "<pre class=\"language-python\">int(\"123\") == 123</pre>", |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 1029 | mandatoryPositionals = { |
Laurent Le Brun | f4648de | 2015-05-07 14:00:32 +0000 | [diff] [blame] | 1030 | @Param(name = "x", type = Object.class, doc = "The string to convert.")}, |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 1031 | useLocation = true) |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 1032 | private static BuiltinFunction int_ = new BuiltinFunction("int") { |
Laurent Le Brun | f4648de | 2015-05-07 14:00:32 +0000 | [diff] [blame] | 1033 | public Integer invoke(Object x, Location loc) throws EvalException { |
| 1034 | if (x instanceof Boolean) { |
| 1035 | return ((Boolean) x).booleanValue() ? 1 : 0; |
| 1036 | } else if (x instanceof Integer) { |
| 1037 | return (Integer) x; |
| 1038 | } else if (x instanceof String) { |
| 1039 | try { |
| 1040 | return Integer.parseInt((String) x); |
| 1041 | } catch (NumberFormatException e) { |
| 1042 | throw new EvalException(loc, |
Francois-Rene Rideau | d61f531 | 2015-06-13 03:34:47 +0000 | [diff] [blame] | 1043 | "invalid literal for int(): " + Printer.repr(x)); |
Laurent Le Brun | f4648de | 2015-05-07 14:00:32 +0000 | [diff] [blame] | 1044 | } |
| 1045 | } else { |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 1046 | throw new EvalException(loc, |
Francois-Rene Rideau | 8d5cce3 | 2015-06-16 23:12:04 +0000 | [diff] [blame] | 1047 | Printer.format("%r is not of type string or int or bool", x)); |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 1048 | } |
| 1049 | } |
| 1050 | }; |
Laurent Le Brun | 0c44aa4 | 2015-04-02 11:32:47 +0000 | [diff] [blame] | 1051 | |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 1052 | @SkylarkSignature(name = "struct", returnType = SkylarkClassObject.class, doc = |
Laurent Le Brun | faf7841 | 2015-07-28 16:13:00 +0000 | [diff] [blame] | 1053 | "Creates an immutable struct using the keyword arguments as attributes. It is used to group " |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1054 | + "multiple values together.Example:<br>" |
Laurent Le Brun | 9d27a01 | 2015-03-31 12:28:02 +0000 | [diff] [blame] | 1055 | + "<pre class=\"language-python\">s = struct(x = 2, y = 3)\n" |
Laurent Le Brun | faf7841 | 2015-07-28 16:13:00 +0000 | [diff] [blame] | 1056 | + "return s.x + getattr(s, \"y\") # returns 5</pre>", |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 1057 | extraKeywords = { |
Laurent Le Brun | faf7841 | 2015-07-28 16:13:00 +0000 | [diff] [blame] | 1058 | @Param(name = "kwargs", doc = "the struct attributes")}, |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 1059 | useLocation = true) |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 1060 | private static BuiltinFunction struct = new BuiltinFunction("struct") { |
| 1061 | @SuppressWarnings("unchecked") |
| 1062 | public SkylarkClassObject invoke(Map<String, Object> kwargs, Location loc) |
| 1063 | throws EvalException, InterruptedException { |
| 1064 | return new SkylarkClassObject(kwargs, loc); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1065 | } |
| 1066 | }; |
| 1067 | |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 1068 | @SkylarkSignature(name = "set", returnType = SkylarkNestedSet.class, |
David Chen | 24f2d99 | 2015-08-17 17:25:46 +0000 | [diff] [blame] | 1069 | doc = "Creates a <a href=\"set.html\">set</a> from the <code>items</code>." |
Laurent Le Brun | 7ace1f2 | 2015-04-09 13:54:21 +0000 | [diff] [blame] | 1070 | + " The set supports nesting other sets of the same element" |
| 1071 | + " type in it. A desired iteration order can also be specified.<br>" |
| 1072 | + " Examples:<br><pre class=\"language-python\">set([\"a\", \"b\"])\n" |
Laszlo Csomor | aded1c2 | 2015-02-16 16:57:12 +0000 | [diff] [blame] | 1073 | + "set([1, 2, 3], order=\"compile\")</pre>", |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 1074 | optionalPositionals = { |
| 1075 | @Param(name = "items", type = Object.class, defaultValue = "[]", |
| 1076 | doc = "The items to initialize the set with. May contain both standalone items " |
| 1077 | + "and other sets."), |
| 1078 | @Param(name = "order", type = String.class, defaultValue = "\"stable\"", |
| 1079 | doc = "The ordering strategy for the set if it's nested, " |
| 1080 | + "possible values are: <code>stable</code> (default), <code>compile</code>, " |
Laurent Le Brun | 5482074 | 2015-07-30 16:43:52 +0000 | [diff] [blame] | 1081 | + "<code>link</code> or <code>naive_link</code>. An explanation of the " |
David Chen | 24f2d99 | 2015-08-17 17:25:46 +0000 | [diff] [blame] | 1082 | + "values can be found <a href=\"set.html\">here</a>.")}, |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 1083 | useLocation = true) |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 1084 | private static final BuiltinFunction set = new BuiltinFunction("set") { |
| 1085 | public SkylarkNestedSet invoke(Object items, String order, |
| 1086 | Location loc) throws EvalException, ConversionException { |
Florian Weikert | fa759e2 | 2015-06-09 12:18:46 +0000 | [diff] [blame] | 1087 | try { |
| 1088 | return new SkylarkNestedSet(Order.parse(order), items, loc); |
| 1089 | } catch (IllegalArgumentException ex) { |
| 1090 | throw new EvalException(loc, ex); |
| 1091 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1092 | } |
| 1093 | }; |
Florian Weikert | a6dae6b | 2015-08-04 20:17:23 +0000 | [diff] [blame] | 1094 | |
| 1095 | @SkylarkSignature(name = "dict", returnType = Map.class, |
| 1096 | doc = |
| 1097 | "Creates a <a href=\"#modules.dict\">dictionary</a> from an optional positional " |
| 1098 | + "argument and an optional set of keyword arguments. Values from the keyword argument " |
| 1099 | + "will overwrite values from the positional argument if a key appears multiple times. " |
| 1100 | + "Dictionaries are always sorted by their keys", |
| 1101 | optionalPositionals = { |
| 1102 | @Param(name = "args", type = Iterable.class, defaultValue = "[]", |
| 1103 | doc = |
| 1104 | "List of entries. Entries must be tuples or lists with exactly " |
| 1105 | + "two elements: key, value"), |
| 1106 | }, |
| 1107 | extraKeywords = {@Param(name = "kwargs", doc = "Dictionary of additional entries.")}, |
| 1108 | useLocation = true) |
| 1109 | private static final BuiltinFunction dict = new BuiltinFunction("dict") { |
| 1110 | @SuppressWarnings("unused") |
| 1111 | public Map<Object, Object> invoke(Iterable<Object> args, Map<Object, Object> kwargs, |
| 1112 | Location loc) throws EvalException, ConversionException { |
| 1113 | try { |
| 1114 | Map<Object, Object> result = new HashMap<>(); |
| 1115 | List<Object> list = Type.OBJECT_LIST.convert(args, "dict(args)"); |
| 1116 | |
| 1117 | for (Object tuple : list) { |
| 1118 | List<Object> mapping = Type.OBJECT_LIST.convert(tuple, "dict(args)"); |
| 1119 | int numElements = mapping.size(); |
| 1120 | |
| 1121 | if (numElements != 2) { |
| 1122 | throw new EvalException( |
| 1123 | location, |
| 1124 | String.format( |
| 1125 | "Tuple has length %d, but exactly two elements are required", numElements)); |
| 1126 | } |
| 1127 | result.put(mapping.get(0), mapping.get(1)); |
| 1128 | } |
| 1129 | result.putAll(kwargs); |
| 1130 | return result; |
| 1131 | } catch (IllegalArgumentException | ClassCastException | NullPointerException ex) { |
| 1132 | throw new EvalException(loc, ex); |
| 1133 | } |
| 1134 | } |
| 1135 | }; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1136 | |
Florian Weikert | 49319f1 | 2015-07-07 17:03:42 +0000 | [diff] [blame] | 1137 | @SkylarkSignature(name = "union", objectType = SkylarkNestedSet.class, |
| 1138 | returnType = SkylarkNestedSet.class, |
David Chen | 24f2d99 | 2015-08-17 17:25:46 +0000 | [diff] [blame] | 1139 | doc = "Creates a new <a href=\"set.html\">set</a> that contains both " |
Florian Weikert | 08c0774 | 2015-06-09 15:06:38 +0000 | [diff] [blame] | 1140 | + "the input set as well as all additional elements.", |
| 1141 | mandatoryPositionals = { |
| 1142 | @Param(name = "input", type = SkylarkNestedSet.class, doc = "The input set"), |
| 1143 | @Param(name = "newElements", type = Iterable.class, doc = "The elements to be added")}, |
| 1144 | useLocation = true) |
| 1145 | private static final BuiltinFunction union = new BuiltinFunction("union") { |
| 1146 | @SuppressWarnings("unused") |
| 1147 | public SkylarkNestedSet invoke(SkylarkNestedSet input, Iterable<Object> newElements, |
| 1148 | Location loc) throws EvalException { |
| 1149 | return new SkylarkNestedSet(input, newElements, loc); |
| 1150 | } |
| 1151 | }; |
Laurent Le Brun | 5482074 | 2015-07-30 16:43:52 +0000 | [diff] [blame] | 1152 | |
Francois-Rene Rideau | 0f7ba34 | 2015-08-31 16:16:21 +0000 | [diff] [blame^] | 1153 | @SkylarkSignature(name = "enumerate", returnType = HackHackEitherList.class, |
Laurent Le Brun | 9ba067d | 2015-05-22 13:55:23 +0000 | [diff] [blame] | 1154 | doc = "Return a list of pairs (two-element tuples), with the index (int) and the item from" |
Laurent Le Brun | 9d27a01 | 2015-03-31 12:28:02 +0000 | [diff] [blame] | 1155 | + " the input list.\n<pre class=\"language-python\">" |
Laurent Le Brun | 9ba067d | 2015-05-22 13:55:23 +0000 | [diff] [blame] | 1156 | + "enumerate([24, 21, 84]) == [(0, 24), (1, 21), (2, 84)]</pre>\n", |
Laurent Le Brun | 5482074 | 2015-07-30 16:43:52 +0000 | [diff] [blame] | 1157 | mandatoryPositionals = { |
| 1158 | @Param(name = "list", type = HackHackEitherList.class, doc = "input list") |
| 1159 | }, |
| 1160 | useLocation = true, |
| 1161 | useEnvironment = true) |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 1162 | private static BuiltinFunction enumerate = new BuiltinFunction("enumerate") { |
Laurent Le Brun | 5482074 | 2015-07-30 16:43:52 +0000 | [diff] [blame] | 1163 | public Object invoke(Object input, Location loc, Environment env) |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 1164 | throws EvalException, ConversionException, InterruptedException { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1165 | int count = 0; |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 1166 | List<SkylarkList> result = Lists.newArrayList(); |
Laurent Le Brun | 5482074 | 2015-07-30 16:43:52 +0000 | [diff] [blame] | 1167 | for (Object obj : Type.OBJECT_LIST.convert(input, "input")) { |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 1168 | result.add(SkylarkList.tuple(count, obj)); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1169 | count++; |
| 1170 | } |
Laurent Le Brun | 5482074 | 2015-07-30 16:43:52 +0000 | [diff] [blame] | 1171 | return convert(result, env, loc); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1172 | } |
| 1173 | }; |
| 1174 | |
Laurent Le Brun | 5482074 | 2015-07-30 16:43:52 +0000 | [diff] [blame] | 1175 | @SkylarkSignature(name = "range", returnType = HackHackEitherList.class, |
Laszlo Csomor | 52fb6a2 | 2015-02-17 09:46:49 +0000 | [diff] [blame] | 1176 | doc = "Creates a list where items go from <code>start</code> to <code>stop</code>, using a " |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1177 | + "<code>step</code> increment. If a single argument is provided, items will " |
| 1178 | + "range from 0 to that element." |
Laurent Le Brun | 9d27a01 | 2015-03-31 12:28:02 +0000 | [diff] [blame] | 1179 | + "<pre class=\"language-python\">range(4) == [0, 1, 2, 3]\n" |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1180 | + "range(3, 9, 2) == [3, 5, 7]\n" |
| 1181 | + "range(3, 0, -1) == [3, 2, 1]</pre>", |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 1182 | mandatoryPositionals = { |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 1183 | @Param(name = "start_or_stop", type = Integer.class, |
| 1184 | doc = "Value of the start element if stop is provided, " |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 1185 | + "otherwise value of stop and the actual start is 0"), |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1186 | }, |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 1187 | optionalPositionals = { |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 1188 | @Param(name = "stop_or_none", type = Integer.class, noneable = true, defaultValue = "None", |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 1189 | doc = "optional index of the first item <i>not</i> to be included in the " |
| 1190 | + "resulting list; generation of the list stops before <code>stop</code> is reached."), |
Francois-Rene Rideau | 534dca1 | 2015-04-21 19:43:19 +0000 | [diff] [blame] | 1191 | @Param(name = "step", type = Integer.class, defaultValue = "1", |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 1192 | doc = "The increment (default is 1). It may be negative.")}, |
Laurent Le Brun | 5482074 | 2015-07-30 16:43:52 +0000 | [diff] [blame] | 1193 | useLocation = true, |
| 1194 | useEnvironment = true) |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 1195 | private static final BuiltinFunction range = new BuiltinFunction("range") { |
Laurent Le Brun | 5482074 | 2015-07-30 16:43:52 +0000 | [diff] [blame] | 1196 | public Object invoke(Integer startOrStop, Object stopOrNone, Integer step, Location loc, |
| 1197 | Environment env) |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 1198 | throws EvalException, ConversionException, InterruptedException { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1199 | int start; |
| 1200 | int stop; |
Francois-Rene Rideau | 0f7ba34 | 2015-08-31 16:16:21 +0000 | [diff] [blame^] | 1201 | if (stopOrNone == Runtime.NONE) { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1202 | start = 0; |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 1203 | stop = startOrStop; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1204 | } else { |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 1205 | start = startOrStop; |
| 1206 | stop = Type.INTEGER.convert(stopOrNone, "'stop' operand of 'range'"); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1207 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1208 | if (step == 0) { |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 1209 | throw new EvalException(loc, "step cannot be 0"); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1210 | } |
| 1211 | List<Integer> result = Lists.newArrayList(); |
| 1212 | if (step > 0) { |
| 1213 | while (start < stop) { |
| 1214 | result.add(start); |
| 1215 | start += step; |
| 1216 | } |
| 1217 | } else { |
| 1218 | while (start > stop) { |
| 1219 | result.add(start); |
| 1220 | start += step; |
| 1221 | } |
| 1222 | } |
Laurent Le Brun | 5482074 | 2015-07-30 16:43:52 +0000 | [diff] [blame] | 1223 | return convert(result, env, loc); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1224 | } |
| 1225 | }; |
| 1226 | |
| 1227 | /** |
| 1228 | * Returns a function-value implementing "select" (i.e. configurable attributes) |
| 1229 | * in the specified package context. |
| 1230 | */ |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 1231 | @SkylarkSignature(name = "select", |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1232 | doc = "Creates a SelectorValue from the dict parameter.", |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 1233 | mandatoryPositionals = { |
| 1234 | @Param(name = "x", type = Map.class, doc = "The parameter to convert.")}) |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 1235 | private static final BuiltinFunction select = new BuiltinFunction("select") { |
| 1236 | public Object invoke(Map<?, ?> dict) throws EvalException, InterruptedException { |
| 1237 | return SelectorList.of(new SelectorValue(dict)); |
| 1238 | } |
| 1239 | }; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1240 | |
| 1241 | /** |
| 1242 | * Returns true if the object has a field of the given name, otherwise false. |
| 1243 | */ |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 1244 | @SkylarkSignature(name = "hasattr", returnType = Boolean.class, |
Laurent Le Brun | faf7841 | 2015-07-28 16:13:00 +0000 | [diff] [blame] | 1245 | doc = "Returns True if the object <code>x</code> has an attribute of the given " |
| 1246 | + "<code>name</code>, otherwise False. Example:<br>" |
Laurent Le Brun | 9d27a01 | 2015-03-31 12:28:02 +0000 | [diff] [blame] | 1247 | + "<pre class=\"language-python\">hasattr(ctx.attr, \"myattr\")</pre>", |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 1248 | mandatoryPositionals = { |
Laurent Le Brun | faf7841 | 2015-07-28 16:13:00 +0000 | [diff] [blame] | 1249 | @Param(name = "x", doc = "The object to check."), |
| 1250 | @Param(name = "name", type = String.class, doc = "The name of the attribute.")}, |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 1251 | useLocation = true, useEnvironment = true) |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 1252 | private static final BuiltinFunction hasattr = new BuiltinFunction("hasattr") { |
| 1253 | public Boolean invoke(Object obj, String name, |
| 1254 | Location loc, Environment env) throws EvalException, ConversionException { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1255 | if (obj instanceof ClassObject && ((ClassObject) obj).getValue(name) != null) { |
| 1256 | return true; |
| 1257 | } |
Francois-Rene Rideau | 0f7ba34 | 2015-08-31 16:16:21 +0000 | [diff] [blame^] | 1258 | if (Runtime.getFunctionNames(obj.getClass()).contains(name)) { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1259 | return true; |
| 1260 | } |
| 1261 | |
| 1262 | try { |
| 1263 | return FuncallExpression.getMethodNames(obj.getClass()).contains(name); |
| 1264 | } catch (ExecutionException e) { |
| 1265 | // This shouldn't happen |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 1266 | throw new EvalException(loc, e.getMessage()); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1267 | } |
| 1268 | } |
| 1269 | }; |
| 1270 | |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 1271 | @SkylarkSignature(name = "getattr", |
Laurent Le Brun | 18d8499 | 2015-08-19 12:13:36 +0000 | [diff] [blame] | 1272 | doc = "Returns the struct's field of the given name if it exists. If not, it either returns " |
| 1273 | + "<code>default</code> (if specified) or raises an error. <code>getattr(x, \"foobar\")" |
| 1274 | + "</code> is equivalent to <code>x.foobar</code>." |
Laurent Le Brun | 9d27a01 | 2015-03-31 12:28:02 +0000 | [diff] [blame] | 1275 | + "<pre class=\"language-python\">getattr(ctx.attr, \"myattr\")\n" |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1276 | + "getattr(ctx.attr, \"myattr\", \"mydefault\")</pre>", |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 1277 | mandatoryPositionals = { |
Laurent Le Brun | faf7841 | 2015-07-28 16:13:00 +0000 | [diff] [blame] | 1278 | @Param(name = "x", doc = "The struct whose attribute is accessed."), |
| 1279 | @Param(name = "name", doc = "The name of the struct attribute.")}, |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 1280 | optionalPositionals = { |
Francois-Rene Rideau | 534dca1 | 2015-04-21 19:43:19 +0000 | [diff] [blame] | 1281 | @Param(name = "default", defaultValue = "None", |
| 1282 | doc = "The default value to return in case the struct " |
Laurent Le Brun | faf7841 | 2015-07-28 16:13:00 +0000 | [diff] [blame] | 1283 | + "doesn't have an attribute of the given name.")}, |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 1284 | useLocation = true) |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 1285 | private static final BuiltinFunction getattr = new BuiltinFunction("getattr") { |
| 1286 | public Object invoke(Object obj, String name, Object defaultValue, |
| 1287 | Location loc) throws EvalException, ConversionException { |
| 1288 | Object result = DotExpression.eval(obj, name, loc); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1289 | if (result == null) { |
Francois-Rene Rideau | 0f7ba34 | 2015-08-31 16:16:21 +0000 | [diff] [blame^] | 1290 | if (defaultValue != Runtime.NONE) { |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 1291 | return defaultValue; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1292 | } else { |
Laurent Le Brun | faf7841 | 2015-07-28 16:13:00 +0000 | [diff] [blame] | 1293 | throw new EvalException(loc, Printer.format("Object of type '%s' has no attribute %r", |
Francois-Rene Rideau | 8d5cce3 | 2015-06-16 23:12:04 +0000 | [diff] [blame] | 1294 | EvalUtils.getDataTypeName(obj), name)); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1295 | } |
| 1296 | } |
| 1297 | return result; |
| 1298 | } |
| 1299 | }; |
| 1300 | |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 1301 | @SkylarkSignature(name = "dir", returnType = SkylarkList.class, |
Laurent Le Brun | faf7841 | 2015-07-28 16:13:00 +0000 | [diff] [blame] | 1302 | doc = "Returns a list strings: the names of the attributes and " |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1303 | + "methods of the parameter object.", |
Laurent Le Brun | faf7841 | 2015-07-28 16:13:00 +0000 | [diff] [blame] | 1304 | mandatoryPositionals = {@Param(name = "x", doc = "The object to check.")}, |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 1305 | useLocation = true, useEnvironment = true) |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 1306 | private static final BuiltinFunction dir = new BuiltinFunction("dir") { |
| 1307 | public SkylarkList invoke(Object object, |
| 1308 | Location loc, Environment env) throws EvalException, ConversionException { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1309 | // Order the fields alphabetically. |
| 1310 | Set<String> fields = new TreeSet<>(); |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 1311 | if (object instanceof ClassObject) { |
| 1312 | fields.addAll(((ClassObject) object).getKeys()); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1313 | } |
Francois-Rene Rideau | 0f7ba34 | 2015-08-31 16:16:21 +0000 | [diff] [blame^] | 1314 | fields.addAll(Runtime.getFunctionNames(object.getClass())); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1315 | try { |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 1316 | fields.addAll(FuncallExpression.getMethodNames(object.getClass())); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1317 | } catch (ExecutionException e) { |
| 1318 | // This shouldn't happen |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 1319 | throw new EvalException(loc, e.getMessage()); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1320 | } |
| 1321 | return SkylarkList.list(fields, String.class); |
| 1322 | } |
| 1323 | }; |
| 1324 | |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 1325 | @SkylarkSignature(name = "type", returnType = String.class, |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1326 | doc = "Returns the type name of its argument.", |
Laurent Le Brun | faf7841 | 2015-07-28 16:13:00 +0000 | [diff] [blame] | 1327 | mandatoryPositionals = {@Param(name = "x", doc = "The object to check type of.")}) |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 1328 | private static final BuiltinFunction type = new BuiltinFunction("type") { |
| 1329 | public String invoke(Object object) { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1330 | // There is no 'type' type in Skylark, so we return a string with the type name. |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 1331 | return EvalUtils.getDataTypeName(object, false); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1332 | } |
| 1333 | }; |
| 1334 | |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 1335 | @SkylarkSignature(name = "fail", |
Laurent Le Brun | faf7841 | 2015-07-28 16:13:00 +0000 | [diff] [blame] | 1336 | doc = "Raises an error that cannot be intercepted. It can be used anywhere, " |
| 1337 | + "both in the loading phase and in the analysis phase.", |
Francois-Rene Rideau | 0f7ba34 | 2015-08-31 16:16:21 +0000 | [diff] [blame^] | 1338 | returnType = Runtime.NoneType.class, |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 1339 | mandatoryPositionals = { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1340 | @Param(name = "msg", type = String.class, doc = "Error message to display for the user")}, |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 1341 | optionalPositionals = { |
| 1342 | @Param(name = "attr", type = String.class, noneable = true, |
| 1343 | defaultValue = "None", |
Laurent Le Brun | faf7841 | 2015-07-28 16:13:00 +0000 | [diff] [blame] | 1344 | doc = "The name of the attribute that caused the error. This is used only for " |
| 1345 | + "error reporting.")}, |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 1346 | useLocation = true) |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 1347 | private static final BuiltinFunction fail = new BuiltinFunction("fail") { |
Francois-Rene Rideau | 0f7ba34 | 2015-08-31 16:16:21 +0000 | [diff] [blame^] | 1348 | public Runtime.NoneType invoke(String msg, Object attr, |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 1349 | Location loc) throws EvalException, ConversionException { |
Francois-Rene Rideau | 0f7ba34 | 2015-08-31 16:16:21 +0000 | [diff] [blame^] | 1350 | if (attr != Runtime.NONE) { |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 1351 | msg = String.format("attribute %s: %s", attr, msg); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1352 | } |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 1353 | throw new EvalException(loc, msg); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1354 | } |
| 1355 | }; |
| 1356 | |
Francois-Rene Rideau | 0f7ba34 | 2015-08-31 16:16:21 +0000 | [diff] [blame^] | 1357 | @SkylarkSignature(name = "print", returnType = Runtime.NoneType.class, |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1358 | doc = "Prints <code>msg</code> to the console.", |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 1359 | optionalNamedOnly = { |
Francois-Rene Rideau | 534dca1 | 2015-04-21 19:43:19 +0000 | [diff] [blame] | 1360 | @Param(name = "sep", type = String.class, defaultValue = "' '", |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 1361 | doc = "The separator string between the objects, default is space (\" \").")}, |
| 1362 | // NB: as compared to Python3, we're missing optional named-only arguments 'end' and 'file' |
| 1363 | extraPositionals = {@Param(name = "args", doc = "The objects to print.")}, |
| 1364 | useLocation = true, useEnvironment = true) |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 1365 | private static final BuiltinFunction print = new BuiltinFunction("print") { |
Francois-Rene Rideau | 0f7ba34 | 2015-08-31 16:16:21 +0000 | [diff] [blame^] | 1366 | public Runtime.NoneType invoke(String sep, SkylarkList starargs, |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 1367 | Location loc, SkylarkEnvironment env) throws EvalException { |
| 1368 | String msg = Joiner.on(sep).join(Iterables.transform(starargs, |
| 1369 | new com.google.common.base.Function<Object, String>() { |
| 1370 | @Override |
| 1371 | public String apply(Object input) { |
Francois-Rene Rideau | d61f531 | 2015-06-13 03:34:47 +0000 | [diff] [blame] | 1372 | return Printer.str(input); |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 1373 | }})); |
| 1374 | env.handleEvent(Event.warn(loc, msg)); |
Francois-Rene Rideau | 0f7ba34 | 2015-08-31 16:16:21 +0000 | [diff] [blame^] | 1375 | return Runtime.NONE; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1376 | } |
| 1377 | }; |
| 1378 | |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 1379 | @SkylarkSignature(name = "zip", |
Googler | c60ec8c | 2015-03-23 14:20:18 +0000 | [diff] [blame] | 1380 | doc = "Returns a <code>list</code> of <code>tuple</code>s, where the i-th tuple contains " |
| 1381 | + "the i-th element from each of the argument sequences or iterables. The list has the " |
| 1382 | + "size of the shortest input. With a single iterable argument, it returns a list of " |
| 1383 | + "1-tuples. With no arguments, it returns an empty list. Examples:" |
Laurent Le Brun | 9d27a01 | 2015-03-31 12:28:02 +0000 | [diff] [blame] | 1384 | + "<pre class=\"language-python\">" |
Googler | c60ec8c | 2015-03-23 14:20:18 +0000 | [diff] [blame] | 1385 | + "zip() # == []\n" |
| 1386 | + "zip([1, 2]) # == [(1,), (2,)]\n" |
| 1387 | + "zip([1, 2], [3, 4]) # == [(1, 3), (2, 4)]\n" |
| 1388 | + "zip([1, 2], [3, 4, 5]) # == [(1, 3), (2, 4)]</pre>", |
Francois-Rene Rideau | a3ac202 | 2015-04-20 18:35:05 +0000 | [diff] [blame] | 1389 | extraPositionals = {@Param(name = "args", doc = "lists to zip")}, |
| 1390 | returnType = SkylarkList.class, useLocation = true) |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 1391 | private static final BuiltinFunction zip = new BuiltinFunction("zip") { |
| 1392 | public SkylarkList invoke(SkylarkList args, Location loc) |
| 1393 | throws EvalException, InterruptedException { |
Googler | c60ec8c | 2015-03-23 14:20:18 +0000 | [diff] [blame] | 1394 | Iterator<?>[] iterators = new Iterator<?>[args.size()]; |
| 1395 | for (int i = 0; i < args.size(); i++) { |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 1396 | iterators[i] = EvalUtils.toIterable(args.get(i), loc).iterator(); |
Googler | c60ec8c | 2015-03-23 14:20:18 +0000 | [diff] [blame] | 1397 | } |
| 1398 | List<SkylarkList> result = new ArrayList<SkylarkList>(); |
| 1399 | boolean allHasNext; |
| 1400 | do { |
| 1401 | allHasNext = !args.isEmpty(); |
| 1402 | List<Object> elem = Lists.newArrayListWithExpectedSize(args.size()); |
| 1403 | for (Iterator<?> iterator : iterators) { |
| 1404 | if (iterator.hasNext()) { |
| 1405 | elem.add(iterator.next()); |
| 1406 | } else { |
| 1407 | allHasNext = false; |
| 1408 | } |
| 1409 | } |
| 1410 | if (allHasNext) { |
| 1411 | result.add(SkylarkList.tuple(elem)); |
| 1412 | } |
| 1413 | } while (allHasNext); |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 1414 | return SkylarkList.list(result, loc); |
Googler | c60ec8c | 2015-03-23 14:20:18 +0000 | [diff] [blame] | 1415 | } |
| 1416 | }; |
| 1417 | |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1418 | /** |
| 1419 | * Skylark String module. |
| 1420 | */ |
| 1421 | @SkylarkModule(name = "string", doc = |
| 1422 | "A language built-in type to support strings. " |
Laurent Le Brun | 4848a65 | 2015-03-18 14:50:12 +0000 | [diff] [blame] | 1423 | + "Examples of string literals:<br>" |
Laurent Le Brun | 9d27a01 | 2015-03-31 12:28:02 +0000 | [diff] [blame] | 1424 | + "<pre class=\"language-python\">a = 'abc\\ndef'\n" |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1425 | + "b = \"ab'cd\"\n" |
Laurent Le Brun | 4848a65 | 2015-03-18 14:50:12 +0000 | [diff] [blame] | 1426 | + "c = \"\"\"multiline string\"\"\"\n" |
| 1427 | + "\n" |
| 1428 | + "# Strings support slicing (negative index starts from the end):\n" |
| 1429 | + "x = \"hello\"[2:4] # \"ll\"\n" |
| 1430 | + "y = \"hello\"[1:-1] # \"ell\"\n" |
| 1431 | + "z = \"hello\"[:4] # \"hell\"</pre>" |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1432 | + "Strings are iterable and support the <code>in</code> operator. Examples:<br>" |
Laurent Le Brun | faf7841 | 2015-07-28 16:13:00 +0000 | [diff] [blame] | 1433 | + "<pre class=\"language-python\">\"bc\" in \"abcd\" # evaluates to True\n" |
Laurent Le Brun | 18d8499 | 2015-08-19 12:13:36 +0000 | [diff] [blame] | 1434 | + "x = [s for s in \"abc\"] # x == [\"a\", \"b\", \"c\"]</pre>\n" |
Laurent Le Brun | 9ba067d | 2015-05-22 13:55:23 +0000 | [diff] [blame] | 1435 | + "Implicit concatenation of strings is not allowed; use the <code>+</code> " |
| 1436 | + "operator instead.") |
Francois-Rene Rideau | 0f7ba34 | 2015-08-31 16:16:21 +0000 | [diff] [blame^] | 1437 | static final class StringModule {} |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1438 | |
| 1439 | /** |
| 1440 | * Skylark Dict module. |
| 1441 | */ |
| 1442 | @SkylarkModule(name = "dict", doc = |
| 1443 | "A language built-in type to support dicts. " |
| 1444 | + "Example of dict literal:<br>" |
Laurent Le Brun | 9d27a01 | 2015-03-31 12:28:02 +0000 | [diff] [blame] | 1445 | + "<pre class=\"language-python\">d = {\"a\": 2, \"b\": 5}</pre>" |
| 1446 | + "Use brackets to access elements:<br>" |
| 1447 | + "<pre class=\"language-python\">e = d[\"a\"] # e == 2</pre>" |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1448 | + "Dicts support the <code>+</code> operator to concatenate two dicts. In case of multiple " |
| 1449 | + "keys the second one overrides the first one. Examples:<br>" |
Laurent Le Brun | 9d27a01 | 2015-03-31 12:28:02 +0000 | [diff] [blame] | 1450 | + "<pre class=\"language-python\">" |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1451 | + "d = {\"a\" : 1} + {\"b\" : 2} # d == {\"a\" : 1, \"b\" : 2}\n" |
| 1452 | + "d += {\"c\" : 3} # d == {\"a\" : 1, \"b\" : 2, \"c\" : 3}\n" |
| 1453 | + "d = d + {\"c\" : 5} # d == {\"a\" : 1, \"b\" : 2, \"c\" : 5}</pre>" |
| 1454 | + "Since the language doesn't have mutable objects <code>d[\"a\"] = 5</code> automatically " |
| 1455 | + "translates to <code>d = d + {\"a\" : 5}</code>.<br>" |
Laurent Le Brun | 9d27a01 | 2015-03-31 12:28:02 +0000 | [diff] [blame] | 1456 | + "Iterating on a dict is equivalent to iterating on its keys (in sorted order).<br>" |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1457 | + "Dicts support the <code>in</code> operator, testing membership in the keyset of the dict. " |
| 1458 | + "Example:<br>" |
Laurent Le Brun | 9d27a01 | 2015-03-31 12:28:02 +0000 | [diff] [blame] | 1459 | + "<pre class=\"language-python\">\"a\" in {\"a\" : 2, \"b\" : 5} # evaluates as True" |
| 1460 | + "</pre>") |
Francois-Rene Rideau | 0f7ba34 | 2015-08-31 16:16:21 +0000 | [diff] [blame^] | 1461 | static final class DictModule {} |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1462 | |
Francois-Rene Rideau | 0f7ba34 | 2015-08-31 16:16:21 +0000 | [diff] [blame^] | 1463 | static final List<BaseFunction> buildGlobalFunctions = ImmutableList.<BaseFunction>of( |
Laurent Le Brun | 2f71fd7 | 2015-08-11 13:35:12 +0000 | [diff] [blame] | 1464 | bool, dict, enumerate, int_, len, list, minus, range, repr, select, sorted, str, zip); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1465 | |
Francois-Rene Rideau | 0f7ba34 | 2015-08-31 16:16:21 +0000 | [diff] [blame^] | 1466 | static final List<BaseFunction> skylarkGlobalFunctions = |
Francois-Rene Rideau | 95b0d0c | 2015-04-22 16:52:13 +0000 | [diff] [blame] | 1467 | ImmutableList.<BaseFunction>builder() |
Francois-Rene Rideau | 0f7ba34 | 2015-08-31 16:16:21 +0000 | [diff] [blame^] | 1468 | .addAll(buildGlobalFunctions) |
Laurent Le Brun | 2f71fd7 | 2015-08-11 13:35:12 +0000 | [diff] [blame] | 1469 | .add(dir, fail, getattr, hasattr, print, set, struct, type) |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1470 | .build(); |
| 1471 | |
Francois-Rene Rideau | 0f7ba34 | 2015-08-31 16:16:21 +0000 | [diff] [blame^] | 1472 | |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1473 | /** |
| 1474 | * Set up a given environment for supported class methods. |
| 1475 | */ |
| 1476 | public static void setupMethodEnvironment(Environment env) { |
Francois-Rene Rideau | 0f7ba34 | 2015-08-31 16:16:21 +0000 | [diff] [blame^] | 1477 | setupMethodEnvironment(env, env.isSkylark() ? skylarkGlobalFunctions : buildGlobalFunctions); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1478 | } |
| 1479 | |
Francois-Rene Rideau | 95b0d0c | 2015-04-22 16:52:13 +0000 | [diff] [blame] | 1480 | private static void setupMethodEnvironment(Environment env, Iterable<BaseFunction> functions) { |
| 1481 | for (BaseFunction function : functions) { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1482 | env.update(function.getName(), function); |
| 1483 | } |
| 1484 | } |
| 1485 | |
Laurent Le Brun | 89bdeaa | 2015-04-14 14:41:59 +0000 | [diff] [blame] | 1486 | /** |
| 1487 | * Collect global functions for the validation environment. |
| 1488 | */ |
Laurent Le Brun | 352b9da | 2015-04-16 14:59:59 +0000 | [diff] [blame] | 1489 | public static void setupValidationEnvironment(Set<String> builtIn) { |
Francois-Rene Rideau | 95b0d0c | 2015-04-22 16:52:13 +0000 | [diff] [blame] | 1490 | for (BaseFunction function : skylarkGlobalFunctions) { |
Laurent Le Brun | 352b9da | 2015-04-16 14:59:59 +0000 | [diff] [blame] | 1491 | builtIn.add(function.getName()); |
Laurent Le Brun | 89bdeaa | 2015-04-14 14:41:59 +0000 | [diff] [blame] | 1492 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1493 | } |
Francois-Rene Rideau | 537a90b | 2015-04-22 06:47:31 +0000 | [diff] [blame] | 1494 | |
| 1495 | static { |
| 1496 | SkylarkSignatureProcessor.configureSkylarkFunctions(MethodLibrary.class); |
| 1497 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1498 | } |