diff options
author | 2015-07-29 18:50:50 +0000 | |
---|---|---|
committer | 2015-07-30 11:31:54 +0000 | |
commit | 3679556ef976b93a860f8618d8170f59dfb6a50c (patch) | |
tree | bcebcc21ce50f310f701250bf95b35dd69f05142 /src/main/java/com/google/devtools/build/lib/packages | |
parent | 53010026f6b1493a4baaf326da4263f2d63fe8d1 (diff) |
Move MethodLibrary from packages to syntax
--
MOS_MIGRATED_REVID=99396919
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/packages')
4 files changed, 2 insertions, 1655 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/packages/FormatParser.java b/src/main/java/com/google/devtools/build/lib/packages/FormatParser.java deleted file mode 100644 index cee1717b86..0000000000 --- a/src/main/java/com/google/devtools/build/lib/packages/FormatParser.java +++ /dev/null @@ -1,294 +0,0 @@ -// Copyright 2014 Google Inc. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -package com.google.devtools.build.lib.packages; - -import com.google.common.collect.ImmutableSet; -import com.google.devtools.build.lib.events.Location; -import com.google.devtools.build.lib.syntax.EvalException; -import com.google.devtools.build.lib.syntax.Printer; - -import java.util.List; -import java.util.Map; - -/** - * A helper class that offers a subset of the functionality of Python's string#format. - * - * <p> Currently, both manual and automatic positional as well as named replacement - * fields are supported. However, nested replacement fields are not allowed. - */ -public final class FormatParser { - - private static final ImmutableSet<Character> ILLEGAL_IN_FIELD = - ImmutableSet.of('.', '[', ']', ','); - - private final Location location; - - public FormatParser(Location location) { - this.location = location; - } - - /** - * Formats the given input string by using the given arguments - * - * <p>This method offers a subset of the functionality of Python's string#format - * - * @param input The string to be formatted - * @param args Positional arguments - * @param kwargs Named arguments - * @return The formatted string - */ - public String format(String input, List<Object> args, Map<String, Object> kwargs) - throws EvalException { - char[] chars = input.toCharArray(); - StringBuilder output = new StringBuilder(); - History history = new History(); - - for (int pos = 0; pos < chars.length; ++pos) { - char current = chars[pos]; - int advancePos = 0; - - if (current == '{') { - advancePos = processOpeningBrace(chars, pos, args, kwargs, history, output); - } else if (current == '}') { - advancePos = processClosingBrace(chars, pos, output); - } else { - output.append(current); - } - - pos += advancePos; - } - - return output.toString(); - } - - /** - * Processes the expression after an opening brace (possibly a replacement field) and emits - * the result to the output StringBuilder - * - * @param chars The entire string - * @param pos The position of the opening brace - * @param args List of positional arguments - * @param kwargs Map of named arguments - * @param history Helper object that tracks information about previously seen positional - * replacement fields - * @param output StringBuilder that consumes the result - * @return Number of characters that have been consumed by this method - */ - protected int processOpeningBrace(char[] chars, int pos, List<Object> args, - Map<String, Object> kwargs, History history, StringBuilder output) throws EvalException { - if (has(chars, pos + 1, '{')) { - // Escaped brace -> output and move to char after right brace - output.append("{"); - return 1; - } - - // Inside a replacement field - String key = getFieldName(chars, pos); - Object value = null; - - // Only positional replacement fields will lead to a valid index - try { - int index = parsePositional(key, history); - - if (index < 0 || index >= args.size()) { - fail("No replacement found for index " + index); - } - - value = args.get(index); - } catch (NumberFormatException nfe) { - // Non-integer index -> Named - if (!kwargs.containsKey(key)) { - fail("Missing argument '" + key + "'"); - } - - value = kwargs.get(key); - } - - // Format object for output - output.append(Printer.str(value)); - - // Advances the current position to the index of the closing brace of the - // replacement field. Due to the definition of the enclosing for() loop, - // the next iteration will examine the character right after the brace. - return key.length() + 1; - } - - /** - * Processes a closing brace and emits the result to the output StringBuilder - * - * @param chars The entire string - * @param pos Position of the closing brace - * @param output StringBuilder that consumes the result - * @return Number of characters that have been consumed by this method - */ - protected int processClosingBrace(char[] chars, int pos, StringBuilder output) - throws EvalException { - if (!has(chars, pos + 1, '}')) { - // Invalid brace outside replacement field - fail("Found '}' without matching '{'"); - } - - // Escaped brace -> output and move to char after right brace - output.append("}"); - return 1; - } - - /** - * Checks whether the given input string has a specific character at the given location - * - * @param data Input string as character array - * @param pos Position to be checked - * @param needle Character to be searched for - * @return True if string has the specified character at the given location - */ - protected boolean has(char[] data, int pos, char needle) { - return pos < data.length && data[pos] == needle; - } - - /** - * Extracts the name/index of the replacement field that starts at the specified location - * - * @param chars Input string - * @param openingBrace Position of the opening brace of the replacement field - * @return Name or index of the current replacement field - */ - protected String getFieldName(char[] chars, int openingBrace) throws EvalException { - StringBuilder result = new StringBuilder(); - boolean foundClosingBrace = false; - - for (int pos = openingBrace + 1; pos < chars.length; ++pos) { - char current = chars[pos]; - - if (current == '}') { - foundClosingBrace = true; - break; - } else { - if (current == '{') { - fail("Nested replacement fields are not supported"); - } else if (ILLEGAL_IN_FIELD.contains(current)) { - fail("Invalid character '" + current + "' inside replacement field"); - } - - result.append(current); - } - } - - if (!foundClosingBrace) { - fail("Found '{' without matching '}'"); - } - - return result.toString(); - } - - /** - * Converts the given key into an integer or assigns the next available index, if empty. - * - * @param key Key to be converted - * @param history Helper object that tracks information about previously seen positional - * replacement fields - * @return The integer equivalent of the key - */ - protected int parsePositional(String key, History history) - throws NumberFormatException, EvalException { - int result = -1; - - try { - if (key.isEmpty()) { - // Automatic positional -> a new index value has to be assigned - history.setAutomaticPositional(); - result = history.getNextPosition(); - } else { - // This will fail if key is a named argument - result = Integer.parseInt(key); - history.setManualPositional(); // Only register if the conversion succeeds - } - } catch (MixedTypeException mte) { - fail(mte.getMessage()); - } - - return result; - } - - /** - * Throws an exception with the specified error message - * @param msg The message to be thrown - */ - protected void fail(String msg) throws EvalException { - throw new EvalException(location, msg); - } - - /** - * Exception for invalid combinations of replacement field types - */ - private static final class MixedTypeException extends Exception { - public MixedTypeException() { - super("Cannot mix manual and automatic numbering of positional fields"); - } - } - - /** - * A wrapper to keep track of information about previous replacement fields - */ - private static final class History { - /** - * Different types of positional replacement fields - */ - private enum Positional { - NONE, - MANUAL, // {0}, {1} etc. - AUTOMATIC // {} - } - - private Positional type = Positional.NONE; - private int position = -1; - - /** - * Returns the next available index for an automatic positional replacement field - * @return Next index - */ - public int getNextPosition() { - ++position; - return position; - } - - /** - * Registers a manual positional replacement field - */ - public void setManualPositional() throws MixedTypeException { - setPositional(Positional.MANUAL); - } - - /** - * Registers an automatic positional replacement field - */ - public void setAutomaticPositional() throws MixedTypeException { - setPositional(Positional.AUTOMATIC); - } - - /** - * Indicates that a positional replacement field of the specified type is being - * processed and checks whether this conflicts with any previously seen - * replacement fields - * - * @param current Type of current replacement field - */ - protected void setPositional(Positional current) throws MixedTypeException { - if (type == Positional.NONE) { - type = current; - } else if (type != current) { - throw new MixedTypeException(); - } - } - } -} diff --git a/src/main/java/com/google/devtools/build/lib/packages/MethodLibrary.java b/src/main/java/com/google/devtools/build/lib/packages/MethodLibrary.java deleted file mode 100644 index aad84fea0b..0000000000 --- a/src/main/java/com/google/devtools/build/lib/packages/MethodLibrary.java +++ /dev/null @@ -1,1361 +0,0 @@ -// Copyright 2014 Google Inc. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package com.google.devtools.build.lib.packages; - -import com.google.common.base.Joiner; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; -import com.google.common.collect.Ordering; -import com.google.devtools.build.lib.collect.nestedset.Order; -import com.google.devtools.build.lib.events.Event; -import com.google.devtools.build.lib.events.Location; -import com.google.devtools.build.lib.packages.Type.ConversionException; -import com.google.devtools.build.lib.syntax.BaseFunction; -import com.google.devtools.build.lib.syntax.BuiltinFunction; -import com.google.devtools.build.lib.syntax.ClassObject; -import com.google.devtools.build.lib.syntax.ClassObject.SkylarkClassObject; -import com.google.devtools.build.lib.syntax.DotExpression; -import com.google.devtools.build.lib.syntax.Environment; -import com.google.devtools.build.lib.syntax.EvalException; -import com.google.devtools.build.lib.syntax.EvalUtils; -import com.google.devtools.build.lib.syntax.FuncallExpression; -import com.google.devtools.build.lib.syntax.Printer; -import com.google.devtools.build.lib.syntax.SelectorList; -import com.google.devtools.build.lib.syntax.SelectorValue; -import com.google.devtools.build.lib.syntax.SkylarkEnvironment; -import com.google.devtools.build.lib.syntax.SkylarkList; -import com.google.devtools.build.lib.syntax.SkylarkModule; -import com.google.devtools.build.lib.syntax.SkylarkNestedSet; -import com.google.devtools.build.lib.syntax.SkylarkSignature; -import com.google.devtools.build.lib.syntax.SkylarkSignature.Param; -import com.google.devtools.build.lib.syntax.SkylarkSignatureProcessor; -import com.google.devtools.build.lib.syntax.SkylarkSignatureProcessor.HackHackEitherList; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; -import java.util.TreeSet; -import java.util.concurrent.ExecutionException; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * A helper class containing built in functions for the Build and the Build Extension Language. - */ -public class MethodLibrary { - - private MethodLibrary() {} - - // TODO(bazel-team): - // the Build language and Skylark currently have different list types: - // the Build language uses plain java List (usually ArrayList) which is mutable and accepts - // any argument, whereas Skylark uses SkylarkList which is immutable and accepts only - // arguments of the same kind. Some methods below use HackHackEitherList as a magic marker - // to indicate that either kind of lists is used depending on the evaluation context. - // It might be a good idea to either have separate methods for the two languages where it matters, - // or to unify the two languages so they use the same data structure (which might require - // updating all existing clients). - - // Convert string index in the same way Python does. - // If index is negative, starts from the end. - // If index is outside bounds, it is restricted to the valid range. - private static int clampIndex(int index, int length) { - if (index < 0) { - index += length; - } - return Math.max(Math.min(index, length), 0); - } - - // Emulate Python substring function - // It converts out of range indices, and never fails - private static String pythonSubstring(String str, int start, Object end, String msg) - throws ConversionException { - if (start == 0 && EvalUtils.isNullOrNone(end)) { - return str; - } - start = clampIndex(start, str.length()); - int stop; - if (EvalUtils.isNullOrNone(end)) { - stop = str.length(); - } else { - stop = clampIndex(Type.INTEGER.convert(end, msg), str.length()); - } - if (start >= stop) { - return ""; - } - return str.substring(start, stop); - } - - public static int getListIndex(Object key, int listSize, Location loc) - throws ConversionException, EvalException { - // Get the nth element in the list - int index = Type.INTEGER.convert(key, "index operand"); - if (index < 0) { - index += listSize; - } - if (index < 0 || index >= listSize) { - throw new EvalException(loc, "List index out of range (index is " - + index + ", but list has " + listSize + " elements)"); - } - return index; - } - - // supported string methods - - @SkylarkSignature(name = "join", objectType = StringModule.class, returnType = String.class, - doc = "Returns a string in which the string elements of the argument have been " - + "joined by this string as a separator. Example:<br>" - + "<pre class=\"language-python\">\"|\".join([\"a\", \"b\", \"c\"]) == \"a|b|c\"</pre>", - mandatoryPositionals = { - @Param(name = "self", type = String.class, doc = "This string, a separator."), - @Param(name = "elements", type = HackHackEitherList.class, doc = "The objects to join.")}) - private static BuiltinFunction join = new BuiltinFunction("join") { - public String invoke(String self, Object elements) throws ConversionException { - List<?> seq = Type.OBJECT_LIST.convert(elements, "'join.elements' argument"); - return Joiner.on(self).join(seq); - } - }; - - @SkylarkSignature(name = "lower", objectType = StringModule.class, returnType = String.class, - doc = "Returns the lower case version of this string.", - mandatoryPositionals = { - @Param(name = "self", type = String.class, doc = "This string, to convert to lower case.")}) - private static BuiltinFunction lower = new BuiltinFunction("lower") { - public String invoke(String self) { - return self.toLowerCase(); - } - }; - - @SkylarkSignature(name = "upper", objectType = StringModule.class, returnType = String.class, - doc = "Returns the upper case version of this string.", - mandatoryPositionals = { - @Param(name = "self", type = String.class, doc = "This string, to convert to upper case.")}) - private static BuiltinFunction upper = new BuiltinFunction("upper") { - public String invoke(String self) { - return self.toUpperCase(); - } - }; - - @SkylarkSignature(name = "replace", objectType = StringModule.class, returnType = String.class, - doc = "Returns a copy of the string in which the occurrences " - + "of <code>old</code> have been replaced with <code>new</code>, optionally restricting " - + "the number of replacements to <code>maxsplit</code>.", - mandatoryPositionals = { - @Param(name = "self", type = String.class, doc = "This string."), - @Param(name = "old", type = String.class, doc = "The string to be replaced."), - @Param(name = "new", type = String.class, doc = "The string to replace with.")}, - optionalPositionals = { - @Param(name = "maxsplit", type = Integer.class, noneable = true, defaultValue = "None", - doc = "The maximum number of replacements.")}, - useLocation = true) - private static BuiltinFunction replace = new BuiltinFunction("replace") { - public String invoke(String self, String oldString, String newString, Object maxSplitO, - Location loc) throws EvalException, ConversionException { - StringBuffer sb = new StringBuffer(); - Integer maxSplit = Type.INTEGER.convertOptional( - maxSplitO, "'maxsplit' argument of 'replace'", /*label*/null, Integer.MAX_VALUE); - try { - Matcher m = Pattern.compile(oldString, Pattern.LITERAL).matcher(self); - for (int i = 0; i < maxSplit && m.find(); i++) { - m.appendReplacement(sb, Matcher.quoteReplacement(newString)); - } - m.appendTail(sb); - } catch (IllegalStateException e) { - throw new EvalException(loc, e.getMessage() + " in call to replace"); - } - return sb.toString(); - } - }; - - @SkylarkSignature(name = "split", objectType = StringModule.class, - returnType = HackHackEitherList.class, - doc = "Returns a list of all the words in the string, using <code>sep</code> " - + "as the separator, optionally limiting the number of splits to <code>maxsplit</code>.", - mandatoryPositionals = { - @Param(name = "self", type = String.class, doc = "This string."), - @Param(name = "sep", type = String.class, doc = "The string to split on.")}, - optionalPositionals = { - @Param(name = "maxsplit", type = Integer.class, noneable = true, defaultValue = "None", - doc = "The maximum number of splits.")}, - useEnvironment = true, - useLocation = true) - private static BuiltinFunction split = new BuiltinFunction("split") { - public Object invoke(String self, String sep, Object maxSplitO, Location loc, - Environment env) throws ConversionException, EvalException { - int maxSplit = Type.INTEGER.convertOptional( - maxSplitO, "'split' argument of 'split'", /*label*/null, -2); - // + 1 because the last result is the remainder, and default of -2 so that after +1 it's -1 - String[] ss = Pattern.compile(sep, Pattern.LITERAL).split(self, maxSplit + 1); - return convert(Arrays.<String>asList(ss), env, loc); - } - }; - - @SkylarkSignature(name = "rsplit", objectType = StringModule.class, - returnType = HackHackEitherList.class, - doc = "Returns a list of all the words in the string, using <code>sep</code> " - + "as the separator, optionally limiting the number of splits to <code>maxsplit</code>. " - + "Except for splitting from the right, this method behaves like split().", - mandatoryPositionals = { - @Param(name = "self", type = String.class, doc = "This string."), - @Param(name = "sep", type = String.class, doc = "The string to split on.")}, - optionalPositionals = { - @Param(name = "maxsplit", type = Integer.class, noneable = true, - defaultValue = "None", doc = "The maximum number of splits.")}, - useEnvironment = true, - useLocation = true) - private static BuiltinFunction rsplit = new BuiltinFunction("rsplit") { - @SuppressWarnings("unused") - public Object invoke(String self, String sep, Object maxSplitO, Location loc, Environment env) - throws ConversionException, EvalException { - int maxSplit = - Type.INTEGER.convertOptional(maxSplitO, "'split' argument of 'split'", null, -1); - List<String> result; - - try { - result = stringRSplit(self, sep, maxSplit); - } catch (IllegalArgumentException ex) { - throw new EvalException(loc, ex); - } - - return convert(result, env, loc); - } - }; - - /** - * Splits the given string into a list of words, using {@code separator} as a - * delimiter. - * - * <p>At most {@code maxSplits} will be performed, going from right to left. - * - * @param input The input string. - * @param separator The separator string. - * @param maxSplits The maximum number of splits. Negative values mean unlimited splits. - * @return A list of words - * @throws IllegalArgumentException - */ - private static List<String> stringRSplit(String input, String separator, int maxSplits) - throws IllegalArgumentException { - if (separator.isEmpty()) { - throw new IllegalArgumentException("Empty separator"); - } - - if (maxSplits <= 0) { - maxSplits = Integer.MAX_VALUE; - } - - LinkedList<String> result = new LinkedList<>(); - String[] parts = input.split(Pattern.quote(separator), -1); - int sepLen = separator.length(); - int remainingLength = input.length(); - int splitsSoFar = 0; - - // Copies parts from the array into the final list, starting at the end (because - // it's rsplit), as long as fewer than maxSplits splits are performed. The - // last spot in the list is reserved for the remaining string, whose length - // has to be tracked throughout the loop. - for (int pos = parts.length - 1; (pos >= 0) && (splitsSoFar < maxSplits); --pos) { - String current = parts[pos]; - result.addFirst(current); - - ++splitsSoFar; - remainingLength -= sepLen + current.length(); - } - - if (splitsSoFar == maxSplits && remainingLength >= 0) { - result.addFirst(input.substring(0, remainingLength)); - } - - return result; - } - - @SkylarkSignature(name = "partition", objectType = StringModule.class, - returnType = HackHackEitherList.class, - doc = "Splits the input string at the first occurrence of the separator " - + "<code>sep</code> and returns the resulting partition as a three-element " - + "list of the form [substring_before, separator, substring_after].", - mandatoryPositionals = { - @Param(name = "self", type = String.class, doc = "This string.")}, - optionalPositionals = { - @Param(name = "sep", type = String.class, - defaultValue = "' '", doc = "The string to split on, default is space (\" \").")}, - useEnvironment = true, - useLocation = true) - private static BuiltinFunction partition = new BuiltinFunction("partition") { - @SuppressWarnings("unused") - public Object invoke(String self, String sep, Location loc, Environment env) - throws EvalException { - return partitionWrapper(self, sep, true, env, loc); - } - }; - - @SkylarkSignature(name = "rpartition", objectType = StringModule.class, - returnType = HackHackEitherList.class, - doc = "Splits the input string at the last occurrence of the separator " - + "<code>sep</code> and returns the resulting partition as a three-element " - + "list of the form [substring_before, separator, substring_after].", - mandatoryPositionals = { - @Param(name = "self", type = String.class, doc = "This string.")}, - optionalPositionals = { - @Param(name = "sep", type = String.class, - defaultValue = "' '", doc = "The string to split on, default is space (\" \").")}, - useEnvironment = true, - useLocation = true) - private static BuiltinFunction rpartition = new BuiltinFunction("rpartition") { - @SuppressWarnings("unused") - public Object invoke(String self, String sep, Location loc, Environment env) - throws EvalException { - return partitionWrapper(self, sep, false, env, loc); - } - }; - - /** - * Wraps the stringPartition() method and converts its results and exceptions - * to the expected types. - * - * @param self The input string - * @param separator The string to split on - * @param forward A flag that controls whether the input string is split around - * the first ({@code true}) or last ({@code false}) occurrence of the separator. - * @param env The current environment - * @param loc The location that is used for potential exceptions - * @return A list with three elements - */ - private static Object partitionWrapper(String self, String separator, boolean forward, - Environment env, Location loc) throws EvalException { - try { - return convert(stringPartition(self, separator, forward), env, loc); - } catch (IllegalArgumentException ex) { - throw new EvalException(loc, ex); - } - } - - /** - * Splits the input string at the {first|last} occurrence of the given separator - * and returns the resulting partition as a three-tuple of Strings, contained - * in a {@code List}. - * - * <p>If the input string does not contain the separator, the tuple will - * consist of the original input string and two empty strings. - * - * <p>This method emulates the behavior of Python's str.partition() and - * str.rpartition(), depending on the value of the {@code forward} flag. - * - * @param input The input string - * @param separator The string to split on - * @param forward A flag that controls whether the input string is split around - * the first ({@code true}) or last ({@code false}) occurrence of the separator. - * @return A three-tuple (List) of the form [part_before_separator, separator, - * part_after_separator]. - * - */ - private static List<String> stringPartition(String input, String separator, boolean forward) - throws IllegalArgumentException { - if (separator.isEmpty()) { - throw new IllegalArgumentException("Empty separator"); - } - - int partitionSize = 3; - ArrayList<String> result = new ArrayList<>(partitionSize); - int pos = forward ? input.indexOf(separator) : input.lastIndexOf(separator); - - if (pos < 0) { - for (int i = 0; i < partitionSize; ++i) { - result.add(""); - } - - // Following Python's implementation of str.partition() and str.rpartition(), - // the input string is copied to either the first or the last position in the - // list, depending on the value of the forward flag. - result.set(forward ? 0 : partitionSize - 1, input); - } else { - result.add(input.substring(0, pos)); - result.add(separator); - - // pos + sep.length() is at most equal to input.length(). This worst-case - // happens when the separator is at the end of the input string. However, - // substring() will return an empty string in this scenario, thus making - // any additional safety checks obsolete. - result.add(input.substring(pos + separator.length())); - } - - return result; - } - - /** - * Common implementation for find, rfind, index, rindex. - * @param forward true if we want to return the last matching index. - */ - private static int stringFind(boolean forward, - String self, String sub, int start, Object end, String msg) - throws ConversionException { - String substr = pythonSubstring(self, start, end, msg); - int subpos = forward ? substr.indexOf(sub) : substr.lastIndexOf(sub); - start = clampIndex(start, self.length()); - return subpos < 0 ? subpos : subpos + start; - } - - @SkylarkSignature(name = "rfind", objectType = StringModule.class, returnType = Integer.class, - doc = "Returns the last index where <code>sub</code> is found, " - + "or -1 if no such index exists, optionally restricting to " - + "[<code>start</code>:<code>end</code>], " - + "<code>start</code> being inclusive and <code>end</code> being exclusive.", - mandatoryPositionals = { - @Param(name = "self", type = String.class, doc = "This string."), - @Param(name = "sub", type = String.class, doc = "The substring to find.")}, - optionalPositionals = { - @Param(name = "start", type = Integer.class, defaultValue = "0", - doc = "Restrict to search from this position."), - @Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None", - doc = "optional position before which to restrict to search.")}) - private static BuiltinFunction rfind = new BuiltinFunction("rfind") { - public Integer invoke(String self, String sub, Integer start, Object end) - throws ConversionException { - return stringFind(false, self, sub, start, end, "'end' argument to rfind"); - } - }; - - @SkylarkSignature(name = "find", objectType = StringModule.class, returnType = Integer.class, - doc = "Returns the first index where <code>sub</code> is found, " - + "or -1 if no such index exists, optionally restricting to " - + "[<code>start</code>:<code>end]</code>, " - + "<code>start</code> being inclusive and <code>end</code> being exclusive.", - mandatoryPositionals = { - @Param(name = "self", type = String.class, doc = "This string."), - @Param(name = "sub", type = String.class, doc = "The substring to find.")}, - optionalPositionals = { - @Param(name = "start", type = Integer.class, defaultValue = "0", - doc = "Restrict to search from this position."), - @Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None", - doc = "optional position before which to restrict to search.")}) - private static BuiltinFunction find = new BuiltinFunction("find") { - public Integer invoke(String self, String sub, Integer start, Object end) - throws ConversionException { - return stringFind(true, self, sub, start, end, "'end' argument to find"); - } - }; - - @SkylarkSignature(name = "rindex", objectType = StringModule.class, returnType = Integer.class, - doc = "Returns the last index where <code>sub</code> is found, " - + "or throw an error if no such index exists, optionally restricting to " - + "[<code>start</code>:<code>end</code>], " - + "<code>start</code> being inclusive and <code>end</code> being exclusive.", - mandatoryPositionals = { - @Param(name = "self", type = String.class, doc = "This string."), - @Param(name = "sub", type = String.class, doc = "The substring to find.")}, - optionalPositionals = { - @Param(name = "start", type = Integer.class, defaultValue = "0", - doc = "Restrict to search from this position."), - @Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None", - doc = "optional position before which to restrict to search.")}, - useLocation = true) - private static BuiltinFunction rindex = new BuiltinFunction("rindex") { - public Integer invoke(String self, String sub, Integer start, Object end, - Location loc) throws EvalException, ConversionException { - int res = stringFind(false, self, sub, start, end, "'end' argument to rindex"); - if (res < 0) { - throw new EvalException(loc, Printer.format("substring %r not found in %r", sub, self)); - } - return res; - } - }; - - @SkylarkSignature(name = "index", objectType = StringModule.class, returnType = Integer.class, - doc = "Returns the first index where <code>sub</code> is found, " - + "or throw an error if no such index exists, optionally restricting to " - + "[<code>start</code>:<code>end]</code>, " - + "<code>start</code> being inclusive and <code>end</code> being exclusive.", - mandatoryPositionals = { - @Param(name = "self", type = String.class, doc = "This string."), - @Param(name = "sub", type = String.class, doc = "The substring to find.")}, - optionalPositionals = { - @Param(name = "start", type = Integer.class, defaultValue = "0", - doc = "Restrict to search from this position."), - @Param(name = "end", type = Integer.class, noneable = true, - doc = "optional position before which to restrict to search.")}, - useLocation = true) - private static BuiltinFunction index = new BuiltinFunction("index") { - public Integer invoke(String self, String sub, Integer start, Object end, - Location loc) throws EvalException, ConversionException { - int res = stringFind(true, self, sub, start, end, "'end' argument to index"); - if (res < 0) { - throw new EvalException(loc, Printer.format("substring %r not found in %r", sub, self)); - } - return res; - } - }; - - @SkylarkSignature(name = "count", objectType = StringModule.class, returnType = Integer.class, - doc = "Returns the number of (non-overlapping) occurrences of substring <code>sub</code> in " - + "string, optionally restricting to [<code>start</code>:<code>end</code>], " - + "<code>start</code> being inclusive and <code>end</code> being exclusive.", - mandatoryPositionals = { - @Param(name = "self", type = String.class, doc = "This string."), - @Param(name = "sub", type = String.class, doc = "The substring to count.")}, - optionalPositionals = { - @Param(name = "start", type = Integer.class, defaultValue = "0", - doc = "Restrict to search from this position."), - @Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None", - doc = "optional position before which to restrict to search.")}) - private static BuiltinFunction count = new BuiltinFunction("count") { - public Integer invoke(String self, String sub, Integer start, Object end) - throws ConversionException { - String str = pythonSubstring(self, start, end, "'end' operand of 'find'"); - if (sub.isEmpty()) { - return str.length() + 1; - } - int count = 0; - int index = -1; - while ((index = str.indexOf(sub)) >= 0) { - count++; - str = str.substring(index + sub.length()); - } - return count; - } - }; - - @SkylarkSignature(name = "endswith", objectType = StringModule.class, returnType = Boolean.class, - doc = "Returns True if the string ends with <code>sub</code>, " - + "otherwise False, optionally restricting to [<code>start</code>:<code>end</code>], " - + "<code>start</code> being inclusive and <code>end</code> being exclusive.", - mandatoryPositionals = { - @Param(name = "self", type = String.class, doc = "This string."), - @Param(name = "sub", type = String.class, doc = "The substring to check.")}, - optionalPositionals = { - @Param(name = "start", type = Integer.class, defaultValue = "0", - doc = "Test beginning at this position."), - @Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None", - doc = "optional position at which to stop comparing.")}) - private static BuiltinFunction endswith = new BuiltinFunction( - "endswith", SkylarkList.tuple(0, Environment.NONE)) { - public Boolean invoke(String self, String sub, Integer start, Object end) - throws ConversionException { - return pythonSubstring(self, start, end, "'end' operand of 'endswith'").endsWith(sub); - } - }; - - // In Python, formatting is very complex. - // We handle here the simplest case which provides most of the value of the function. - // https://docs.python.org/3/library/string.html#formatstrings - @SkylarkSignature(name = "format", objectType = StringModule.class, returnType = String.class, - doc = "Replace the values surrounded by curly brackets in the string." - + "<pre class=\"language-python\">" - + "\"x{key}x\".format(key = 2) == \"x2x\"</pre>\n", - mandatoryPositionals = { - @Param(name = "self", type = String.class, doc = "This string."), - }, - extraPositionals = { - @Param(name = "args", type = HackHackEitherList.class, defaultValue = "[]", - doc = "List of arguments"), - }, - extraKeywords = {@Param(name = "kwargs", doc = "Dictionary of arguments")}, - useLocation = true) - private static BuiltinFunction format = new BuiltinFunction("format") { - @SuppressWarnings("unused") - public String invoke(String self, Object args, Map<String, Object> kwargs, Location loc) - throws ConversionException, EvalException { - return new FormatParser(loc).format(self, Type.OBJECT_LIST.convert(args, "format"), kwargs); - } - }; - - @SkylarkSignature(name = "startswith", objectType = StringModule.class, - returnType = Boolean.class, - doc = "Returns True if the string starts with <code>sub</code>, " - + "otherwise False, optionally restricting to [<code>start</code>:<code>end</code>], " - + "<code>start</code> being inclusive and <code>end</code> being exclusive.", - mandatoryPositionals = { - @Param(name = "self", type = String.class, doc = "This string."), - @Param(name = "sub", type = String.class, doc = "The substring to check.")}, - optionalPositionals = { - @Param(name = "start", type = Integer.class, defaultValue = "0", - doc = "Test beginning at this position."), - @Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None", - doc = "Stop comparing at this position.")}) - private static BuiltinFunction startswith = new BuiltinFunction("startswith") { - public Boolean invoke(String self, String sub, Integer start, Object end) - throws ConversionException { - return pythonSubstring(self, start, end, "'end' operand of 'startswith'").startsWith(sub); - } - }; - - // TODO(bazel-team): Maybe support an argument to tell the type of the whitespace. - @SkylarkSignature(name = "strip", objectType = StringModule.class, returnType = String.class, - doc = "Returns a copy of the string in which all whitespace characters " - + "have been stripped from the beginning and the end of the string.", - mandatoryPositionals = { - @Param(name = "self", type = String.class, doc = "This string.")}) - private static BuiltinFunction strip = new BuiltinFunction("strip") { - public String invoke(String self) { - return self.trim(); - } - }; - - // slice operator - @SkylarkSignature(name = "$slice", documented = false, - mandatoryPositionals = { - @Param(name = "self", type = Object.class, doc = "This string, list or tuple."), - @Param(name = "start", type = Integer.class, doc = "start position of the slice."), - @Param(name = "end", type = Integer.class, doc = "end position of the slice.")}, - doc = "x[<code>start</code>:<code>end</code>] returns a slice or a list slice.", - useLocation = true, useEnvironment = true) - private static BuiltinFunction slice = new BuiltinFunction("$slice") { - public Object invoke(Object self, Integer left, Integer right, - Location loc, Environment env) throws EvalException, ConversionException { - // Substring - if (self instanceof String) { - return pythonSubstring((String) self, left, right, ""); - } - - // List slice - List<Object> list = Type.OBJECT_LIST.convert(self, "list operand"); - left = clampIndex(left, list.size()); - right = clampIndex(right, list.size()); - if (left > right) { - left = right; - } - return convert(list.subList(left, right), env, loc); - } - }; - - // supported list methods - @SkylarkSignature(name = "sorted", returnType = HackHackEitherList.class, - doc = "Sort a collection.", - mandatoryPositionals = { - @Param(name = "self", type = HackHackEitherList.class, doc = "This list.")}, - useLocation = true, useEnvironment = true) - private static BuiltinFunction sorted = new BuiltinFunction("sorted") { - public Object invoke(Object self, Location loc, Environment env) - throws EvalException, ConversionException { - List<Object> list = Type.OBJECT_LIST.convert(self, "'sorted' operand"); - try { - list = Ordering.from(EvalUtils.SKYLARK_COMPARATOR).sortedCopy(list); - } catch (EvalUtils.ComparisonException e) { - throw new EvalException(loc, e); - } - return convert(list, env, loc); - } - }; - - // This function has a SkylarkSignature but is only used by the Build language, not Skylark. - @SkylarkSignature(name = "append", returnType = Environment.NoneType.class, documented = false, - doc = "Adds an item to the end of the list.", - mandatoryPositionals = { - // we use List rather than SkylarkList because this is actually for use *outside* Skylark - @Param(name = "self", type = List.class, doc = "This list."), - @Param(name = "item", type = Object.class, doc = "Item to add at the end.")}, - useLocation = true, useEnvironment = true) - private static BuiltinFunction append = new BuiltinFunction("append") { - public Environment.NoneType invoke(List<Object> self, Object item, - Location loc, Environment env) throws EvalException, ConversionException { - self.add(item); - return Environment.NONE; - } - }; - - // This function has a SkylarkSignature but is only used by the Build language, not Skylark. - @SkylarkSignature(name = "extend", returnType = Environment.NoneType.class, documented = false, - doc = "Adds all items to the end of the list.", - mandatoryPositionals = { - // we use List rather than SkylarkList because this is actually for use *outside* Skylark - @Param(name = "self", type = List.class, doc = "This list."), - @Param(name = "items", type = List.class, doc = "Items to add at the end.")}, - useLocation = true, useEnvironment = true) - private static BuiltinFunction extend = new BuiltinFunction("extend") { - public Environment.NoneType invoke(List<Object> self, List<Object> items, - Location loc, Environment env) throws EvalException, ConversionException { - self.addAll(items); - return Environment.NONE; - } - }; - - // dictionary access operator - @SkylarkSignature(name = "$index", documented = false, - doc = "Returns the nth element of a list or string, " - + "or looks up a value in a dictionary.", - mandatoryPositionals = { - @Param(name = "self", type = Object.class, doc = "This object."), - @Param(name = "key", type = Object.class, doc = "The index or key to access.")}, - useLocation = true) - private static BuiltinFunction indexOperator = new BuiltinFunction("$index") { - public Object invoke(Object self, Object key, - Location loc) throws EvalException, ConversionException { - if (self instanceof SkylarkList) { - SkylarkList list = (SkylarkList) self; - if (list.isEmpty()) { - throw new EvalException(loc, "List is empty"); - } - int index = getListIndex(key, list.size(), loc); - return list.get(index); - - } else if (self instanceof Map<?, ?>) { - Map<?, ?> dictionary = (Map<?, ?>) self; - if (!dictionary.containsKey(key)) { - throw new EvalException(loc, Printer.format("Key %r not found in dictionary", key)); - } - return dictionary.get(key); - - } else if (self instanceof List<?>) { - List<Object> list = Type.OBJECT_LIST.convert(self, "index operand"); - if (list.isEmpty()) { - throw new EvalException(loc, "List is empty"); - } - int index = getListIndex(key, list.size(), loc); - return list.get(index); - - } else if (self instanceof String) { - String str = (String) self; - int index = getListIndex(key, str.length(), loc); - return str.substring(index, index + 1); - - } else { - // TODO(bazel-team): This is dead code, get rid of it. - throw new EvalException(loc, String.format( - "Unsupported datatype (%s) for indexing, only works for dict and list", - EvalUtils.getDataTypeName(self))); - } - } - }; - - @SkylarkSignature(name = "values", objectType = DictModule.class, - returnType = HackHackEitherList.class, - doc = "Return the list of values. Dictionaries are always sorted by their keys:" - + "<pre class=\"language-python\">" - + "{2: \"a\", 4: \"b\", 1: \"c\"}.values() == [\"c\", \"a\", \"b\"]</pre>\n", - mandatoryPositionals = {@Param(name = "self", type = Map.class, doc = "This dict.")}, - useLocation = true, useEnvironment = true) - private static BuiltinFunction values = new BuiltinFunction("values") { - public Object invoke(Map<?, ?> self, - Location loc, Environment env) throws EvalException, ConversionException { - // Use a TreeMap to ensure consistent ordering. - Map<?, ?> dict = new TreeMap<>(self); - return convert(dict.values(), env, loc); - } - }; - - @SkylarkSignature(name = "items", objectType = DictModule.class, - returnType = HackHackEitherList.class, - doc = "Return the list of key-value tuples. Dictionaries are always sorted by their keys:" - + "<pre class=\"language-python\">" - + "{2: \"a\", 4: \"b\", 1: \"c\"}.items() == [(1, \"c\"), (2, \"a\"), (4, \"b\")]" - + "</pre>\n", - mandatoryPositionals = { - @Param(name = "self", type = Map.class, doc = "This dict.")}, - useLocation = true, useEnvironment = true) - private static BuiltinFunction items = new BuiltinFunction("items") { - public Object invoke(Map<?, ?> self, - Location loc, Environment env) throws EvalException, ConversionException { - // Use a TreeMap to ensure consistent ordering. - Map<?, ?> dict = new TreeMap<>(self); - List<Object> list = Lists.newArrayListWithCapacity(dict.size()); - for (Map.Entry<?, ?> entries : dict.entrySet()) { - List<?> item = ImmutableList.of(entries.getKey(), entries.getValue()); - list.add(env.isSkylarkEnabled() ? SkylarkList.tuple(item) : item); - } - return convert(list, env, loc); - } - }; - - @SkylarkSignature(name = "keys", objectType = DictModule.class, - returnType = HackHackEitherList.class, - doc = "Return the list of keys. Dictionaries are always sorted by their keys:" - + "<pre class=\"language-python\">{2: \"a\", 4: \"b\", 1: \"c\"}.keys() == [1, 2, 4]" - + "</pre>\n", - mandatoryPositionals = { - @Param(name = "self", type = Map.class, doc = "This dict.")}, - useLocation = true, useEnvironment = true) - // Skylark will only call this on a dict; and - // allowed keys are all Comparable... if not mutually, it's OK to get a runtime exception. - private static BuiltinFunction keys = new BuiltinFunction("keys") { - public Object invoke(Map<Comparable<?>, ?> dict, - Location loc, Environment env) throws EvalException { - return convert(Ordering.natural().sortedCopy(dict.keySet()), env, loc); - } - }; - - @SkylarkSignature(name = "get", objectType = DictModule.class, - doc = "Return the value for <code>key</code> if <code>key</code> is in the dictionary, " - + "else <code>default</code>. If <code>default</code> is not given, it defaults to " - + "<code>None</code>, so that this method never throws an error.", - mandatoryPositionals = { - @Param(name = "self", doc = "This dict."), - @Param(name = "key", doc = "The key to look for.")}, - optionalPositionals = { - @Param(name = "default", defaultValue = "None", - doc = "The default value to use (instead of None) if the key is not found.")}) - private static BuiltinFunction get = new BuiltinFunction("get") { - public Object invoke(Map<?, ?> self, Object key, Object defaultValue) { - if (self.containsKey(key)) { - return self.get(key); - } - return defaultValue; - } - }; - - // TODO(bazel-team): Use the same type for both Skylark and BUILD files. - @SuppressWarnings("unchecked") - private static Iterable<Object> convert(Collection<?> list, Environment env, Location loc) - throws EvalException { - if (env.isSkylarkEnabled()) { - return SkylarkList.list(list, loc); - } else { - return Lists.newArrayList(list); - } - } - - // unary minus - @SkylarkSignature(name = "-", returnType = Integer.class, - documented = false, doc = "Unary minus operator.", - mandatoryPositionals = { - @Param(name = "num", type = Integer.class, doc = "The number to negate.")}) - private static BuiltinFunction minus = new BuiltinFunction("-") { - public Integer invoke(Integer num) throws ConversionException { - return -num; - } - }; - - @SkylarkSignature(name = "list", returnType = SkylarkList.class, - doc = "Converts a collection (e.g. set or dictionary) to a list." - + "<pre class=\"language-python\">list([1, 2]) == [1, 2]\n" - + "list(set([2, 3, 2])) == [2, 3]\n" - + "list({5: \"a\", 2: \"b\", 4: \"c\"}) == [2, 4, 5]</pre>", - mandatoryPositionals = {@Param(name = "x", doc = "The object to convert.")}, - useLocation = true) - private static BuiltinFunction list = new BuiltinFunction("list") { - public SkylarkList invoke(Object x, Location loc) throws EvalException { - return SkylarkList.list(EvalUtils.toCollection(x, loc), loc); - } - }; - - @SkylarkSignature(name = "len", returnType = Integer.class, doc = - "Returns the length of a string, list, tuple, set, or dictionary.", - mandatoryPositionals = {@Param(name = "x", doc = "The object to check length of.")}, - useLocation = true) - private static BuiltinFunction len = new BuiltinFunction("len") { - public Integer invoke(Object x, Location loc) throws EvalException { - int l = EvalUtils.size(x); - if (l == -1) { - throw new EvalException(loc, EvalUtils.getDataTypeName(x) + " is not iterable"); - } - return l; - } - }; - - @SkylarkSignature(name = "str", returnType = String.class, doc = - "Converts any object to string. This is useful for debugging." - + "<pre class=\"language-python\">str(\"ab\") == \"ab\"</pre>", - mandatoryPositionals = {@Param(name = "x", doc = "The object to convert.")}) - private static BuiltinFunction str = new BuiltinFunction("str") { - public String invoke(Object x) { - return Printer.str(x); - } - }; - - @SkylarkSignature(name = "repr", returnType = String.class, doc = - "Converts any object to a string representation. This is useful for debugging.<br>" - + "<pre class=\"language-python\">str(\"ab\") == \\\"ab\\\"</pre>", - mandatoryPositionals = {@Param(name = "x", doc = "The object to convert.")}) - private static BuiltinFunction repr = new BuiltinFunction("repr") { - public String invoke(Object x) { - return Printer.repr(x); - } - }; - - @SkylarkSignature(name = "bool", returnType = Boolean.class, - doc = "Constructor for the bool type. " - + "It returns False if the object is None, False, an empty string, the number 0, or an " - + "empty collection. Otherwise, it returns True.", - mandatoryPositionals = {@Param(name = "x", doc = "The variable to convert.")}) - private static BuiltinFunction bool = new BuiltinFunction("bool") { - public Boolean invoke(Object x) throws EvalException { - return EvalUtils.toBoolean(x); - } - }; - - @SkylarkSignature(name = "int", returnType = Integer.class, doc = "Converts a value to int. " - + "If the argument is a string, it is converted using base 10 and raises an error if the " - + "conversion fails. If the argument is a bool, it returns 0 (False) or 1 (True). " - + "If the argument is an int, it is simply returned." - + "<pre class=\"language-python\">int(\"123\") == 123</pre>", - mandatoryPositionals = { - @Param(name = "x", type = Object.class, doc = "The string to convert.")}, - useLocation = true) - private static BuiltinFunction int_ = new BuiltinFunction("int") { - public Integer invoke(Object x, Location loc) throws EvalException { - if (x instanceof Boolean) { - return ((Boolean) x).booleanValue() ? 1 : 0; - } else if (x instanceof Integer) { - return (Integer) x; - } else if (x instanceof String) { - try { - return Integer.parseInt((String) x); - } catch (NumberFormatException e) { - throw new EvalException(loc, - "invalid literal for int(): " + Printer.repr(x)); - } - } else { - throw new EvalException(loc, - Printer.format("%r is not of type string or int or bool", x)); - } - } - }; - - @SkylarkSignature(name = "struct", returnType = SkylarkClassObject.class, doc = - "Creates an immutable struct using the keyword arguments as attributes. It is used to group " - + "multiple values together.Example:<br>" - + "<pre class=\"language-python\">s = struct(x = 2, y = 3)\n" - + "return s.x + getattr(s, \"y\") # returns 5</pre>", - extraKeywords = { - @Param(name = "kwargs", doc = "the struct attributes")}, - useLocation = true) - private static BuiltinFunction struct = new BuiltinFunction("struct") { - @SuppressWarnings("unchecked") - public SkylarkClassObject invoke(Map<String, Object> kwargs, Location loc) - throws EvalException, InterruptedException { - return new SkylarkClassObject(kwargs, loc); - } - }; - - @SkylarkSignature(name = "set", returnType = SkylarkNestedSet.class, - doc = "Creates a <a href=\"#modules.set\">set</a> from the <code>items</code>." - + " The set supports nesting other sets of the same element" - + " type in it. A desired iteration order can also be specified.<br>" - + " Examples:<br><pre class=\"language-python\">set([\"a\", \"b\"])\n" - + "set([1, 2, 3], order=\"compile\")</pre>", - optionalPositionals = { - @Param(name = "items", type = Object.class, defaultValue = "[]", - doc = "The items to initialize the set with. May contain both standalone items " - + "and other sets."), - @Param(name = "order", type = String.class, defaultValue = "\"stable\"", - doc = "The ordering strategy for the set if it's nested, " - + "possible values are: <code>stable</code> (default), <code>compile</code>, " - + "<code>link</code> or <code>naive_link</code>. An explanation of the " - + "values can be found <a href=\"#modules.set\">here</a>.")}, - useLocation = true) - private static final BuiltinFunction set = new BuiltinFunction("set") { - public SkylarkNestedSet invoke(Object items, String order, - Location loc) throws EvalException, ConversionException { - try { - return new SkylarkNestedSet(Order.parse(order), items, loc); - } catch (IllegalArgumentException ex) { - throw new EvalException(loc, ex); - } - } - }; - - @SkylarkSignature(name = "union", objectType = SkylarkNestedSet.class, - returnType = SkylarkNestedSet.class, - doc = "Creates a new <a href=\"#modules.set\">set</a> that contains both " - + "the input set as well as all additional elements.", - mandatoryPositionals = { - @Param(name = "input", type = SkylarkNestedSet.class, doc = "The input set"), - @Param(name = "newElements", type = Iterable.class, doc = "The elements to be added")}, - useLocation = true) - private static final BuiltinFunction union = new BuiltinFunction("union") { - @SuppressWarnings("unused") - public SkylarkNestedSet invoke(SkylarkNestedSet input, Iterable<Object> newElements, - Location loc) throws EvalException { - return new SkylarkNestedSet(input, newElements, loc); - } - }; - - @SkylarkSignature(name = "enumerate", returnType = SkylarkList.class, - doc = "Return a list of pairs (two-element tuples), with the index (int) and the item from" - + " the input list.\n<pre class=\"language-python\">" - + "enumerate([24, 21, 84]) == [(0, 24), (1, 21), (2, 84)]</pre>\n", - mandatoryPositionals = {@Param(name = "list", type = SkylarkList.class, doc = "input list")}, - useLocation = true) - private static BuiltinFunction enumerate = new BuiltinFunction("enumerate") { - public SkylarkList invoke(SkylarkList input, Location loc) - throws EvalException, ConversionException, InterruptedException { - int count = 0; - List<SkylarkList> result = Lists.newArrayList(); - for (Object obj : input) { - result.add(SkylarkList.tuple(count, obj)); - count++; - } - return SkylarkList.list(result, loc); - } - }; - - @SkylarkSignature(name = "range", returnType = SkylarkList.class, - doc = "Creates a list where items go from <code>start</code> to <code>stop</code>, using a " - + "<code>step</code> increment. If a single argument is provided, items will " - + "range from 0 to that element." - + "<pre class=\"language-python\">range(4) == [0, 1, 2, 3]\n" - + "range(3, 9, 2) == [3, 5, 7]\n" - + "range(3, 0, -1) == [3, 2, 1]</pre>", - mandatoryPositionals = { - @Param(name = "start_or_stop", type = Integer.class, - doc = "Value of the start element if stop is provided, " - + "otherwise value of stop and the actual start is 0"), - }, - optionalPositionals = { - @Param(name = "stop_or_none", type = Integer.class, noneable = true, defaultValue = "None", - doc = "optional index of the first item <i>not</i> to be included in the " - + "resulting list; generation of the list stops before <code>stop</code> is reached."), - @Param(name = "step", type = Integer.class, defaultValue = "1", - doc = "The increment (default is 1). It may be negative.")}, - useLocation = true) - private static final BuiltinFunction range = new BuiltinFunction("range") { - public SkylarkList invoke(Integer startOrStop, Object stopOrNone, Integer step, Location loc) - throws EvalException, ConversionException, InterruptedException { - int start; - int stop; - if (stopOrNone == Environment.NONE) { - start = 0; - stop = startOrStop; - } else { - start = startOrStop; - stop = Type.INTEGER.convert(stopOrNone, "'stop' operand of 'range'"); - } - if (step == 0) { - throw new EvalException(loc, "step cannot be 0"); - } - List<Integer> result = Lists.newArrayList(); - if (step > 0) { - while (start < stop) { - result.add(start); - start += step; - } - } else { - while (start > stop) { - result.add(start); - start += step; - } - } - return SkylarkList.list(result, Integer.class); - } - }; - - /** - * Returns a function-value implementing "select" (i.e. configurable attributes) - * in the specified package context. - */ - @SkylarkSignature(name = "select", - doc = "Creates a SelectorValue from the dict parameter.", - mandatoryPositionals = { - @Param(name = "x", type = Map.class, doc = "The parameter to convert.")}) - private static final BuiltinFunction select = new BuiltinFunction("select") { - public Object invoke(Map<?, ?> dict) throws EvalException, InterruptedException { - return SelectorList.of(new SelectorValue(dict)); - } - }; - - /** - * Returns true if the object has a field of the given name, otherwise false. - */ - @SkylarkSignature(name = "hasattr", returnType = Boolean.class, - doc = "Returns True if the object <code>x</code> has an attribute of the given " - + "<code>name</code>, otherwise False. Example:<br>" - + "<pre class=\"language-python\">hasattr(ctx.attr, \"myattr\")</pre>", - mandatoryPositionals = { - @Param(name = "x", doc = "The object to check."), - @Param(name = "name", type = String.class, doc = "The name of the attribute.")}, - useLocation = true, useEnvironment = true) - private static final BuiltinFunction hasattr = new BuiltinFunction("hasattr") { - public Boolean invoke(Object obj, String name, - Location loc, Environment env) throws EvalException, ConversionException { - if (obj instanceof ClassObject && ((ClassObject) obj).getValue(name) != null) { - return true; - } - if (env.getFunctionNames(obj.getClass()).contains(name)) { - return true; - } - - try { - return FuncallExpression.getMethodNames(obj.getClass()).contains(name); - } catch (ExecutionException e) { - // This shouldn't happen - throw new EvalException(loc, e.getMessage()); - } - } - }; - - @SkylarkSignature(name = "getattr", - doc = "Returns the struct's field of the given name if exists, otherwise <code>default</code>" - + " if specified, otherwise raises an error. For example, <code>getattr(x, \"foobar\")" - + "</code> is equivalent to <code>x.foobar</code>, except that it returns " - + "<code>default</code> for a non-existent attribute instead of raising an error." - + "<br>" - + "<pre class=\"language-python\">getattr(ctx.attr, \"myattr\")\n" - + "getattr(ctx.attr, \"myattr\", \"mydefault\")</pre>", - mandatoryPositionals = { - @Param(name = "x", doc = "The struct whose attribute is accessed."), - @Param(name = "name", doc = "The name of the struct attribute.")}, - optionalPositionals = { - @Param(name = "default", defaultValue = "None", - doc = "The default value to return in case the struct " - + "doesn't have an attribute of the given name.")}, - useLocation = true) - private static final BuiltinFunction getattr = new BuiltinFunction("getattr") { - public Object invoke(Object obj, String name, Object defaultValue, - Location loc) throws EvalException, ConversionException { - Object result = DotExpression.eval(obj, name, loc); - if (result == null) { - if (defaultValue != Environment.NONE) { - return defaultValue; - } else { - throw new EvalException(loc, Printer.format("Object of type '%s' has no attribute %r", - EvalUtils.getDataTypeName(obj), name)); - } - } - return result; - } - }; - - @SkylarkSignature(name = "dir", returnType = SkylarkList.class, - doc = "Returns a list strings: the names of the attributes and " - + "methods of the parameter object.", - mandatoryPositionals = {@Param(name = "x", doc = "The object to check.")}, - useLocation = true, useEnvironment = true) - private static final BuiltinFunction dir = new BuiltinFunction("dir") { - public SkylarkList invoke(Object object, - Location loc, Environment env) throws EvalException, ConversionException { - // Order the fields alphabetically. - Set<String> fields = new TreeSet<>(); - if (object instanceof ClassObject) { - fields.addAll(((ClassObject) object).getKeys()); - } - fields.addAll(env.getFunctionNames(object.getClass())); - try { - fields.addAll(FuncallExpression.getMethodNames(object.getClass())); - } catch (ExecutionException e) { - // This shouldn't happen - throw new EvalException(loc, e.getMessage()); - } - return SkylarkList.list(fields, String.class); - } - }; - - @SkylarkSignature(name = "type", returnType = String.class, - doc = "Returns the type name of its argument.", - mandatoryPositionals = {@Param(name = "x", doc = "The object to check type of.")}) - private static final BuiltinFunction type = new BuiltinFunction("type") { - public String invoke(Object object) { - // There is no 'type' type in Skylark, so we return a string with the type name. - return EvalUtils.getDataTypeName(object, false); - } - }; - - @SkylarkSignature(name = "fail", - doc = "Raises an error that cannot be intercepted. It can be used anywhere, " - + "both in the loading phase and in the analysis phase.", - returnType = Environment.NoneType.class, - mandatoryPositionals = { - @Param(name = "msg", type = String.class, doc = "Error message to display for the user")}, - optionalPositionals = { - @Param(name = "attr", type = String.class, noneable = true, - defaultValue = "None", - doc = "The name of the attribute that caused the error. This is used only for " - + "error reporting.")}, - useLocation = true) - private static final BuiltinFunction fail = new BuiltinFunction("fail") { - public Environment.NoneType invoke(String msg, Object attr, - Location loc) throws EvalException, ConversionException { - if (attr != Environment.NONE) { - msg = String.format("attribute %s: %s", attr, msg); - } - throw new EvalException(loc, msg); - } - }; - - @SkylarkSignature(name = "print", returnType = Environment.NoneType.class, - doc = "Prints <code>msg</code> to the console.", - optionalNamedOnly = { - @Param(name = "sep", type = String.class, defaultValue = "' '", - doc = "The separator string between the objects, default is space (\" \").")}, - // NB: as compared to Python3, we're missing optional named-only arguments 'end' and 'file' - extraPositionals = {@Param(name = "args", doc = "The objects to print.")}, - useLocation = true, useEnvironment = true) - private static final BuiltinFunction print = new BuiltinFunction("print") { - public Environment.NoneType invoke(String sep, SkylarkList starargs, - Location loc, SkylarkEnvironment env) throws EvalException { - String msg = Joiner.on(sep).join(Iterables.transform(starargs, - new com.google.common.base.Function<Object, String>() { - @Override - public String apply(Object input) { - return Printer.str(input); - }})); - env.handleEvent(Event.warn(loc, msg)); - return Environment.NONE; - } - }; - - @SkylarkSignature(name = "zip", - doc = "Returns a <code>list</code> of <code>tuple</code>s, where the i-th tuple contains " - + "the i-th element from each of the argument sequences or iterables. The list has the " - + "size of the shortest input. With a single iterable argument, it returns a list of " - + "1-tuples. With no arguments, it returns an empty list. Examples:" - + "<pre class=\"language-python\">" - + "zip() # == []\n" - + "zip([1, 2]) # == [(1,), (2,)]\n" - + "zip([1, 2], [3, 4]) # == [(1, 3), (2, 4)]\n" - + "zip([1, 2], [3, 4, 5]) # == [(1, 3), (2, 4)]</pre>", - extraPositionals = {@Param(name = "args", doc = "lists to zip")}, - returnType = SkylarkList.class, useLocation = true) - private static final BuiltinFunction zip = new BuiltinFunction("zip") { - public SkylarkList invoke(SkylarkList args, Location loc) - throws EvalException, InterruptedException { - Iterator<?>[] iterators = new Iterator<?>[args.size()]; - for (int i = 0; i < args.size(); i++) { - iterators[i] = EvalUtils.toIterable(args.get(i), loc).iterator(); - } - List<SkylarkList> result = new ArrayList<SkylarkList>(); - boolean allHasNext; - do { - allHasNext = !args.isEmpty(); - List<Object> elem = Lists.newArrayListWithExpectedSize(args.size()); - for (Iterator<?> iterator : iterators) { - if (iterator.hasNext()) { - elem.add(iterator.next()); - } else { - allHasNext = false; - } - } - if (allHasNext) { - result.add(SkylarkList.tuple(elem)); - } - } while (allHasNext); - return SkylarkList.list(result, loc); - } - }; - - /** - * Skylark String module. - */ - @SkylarkModule(name = "string", doc = - "A language built-in type to support strings. " - + "Examples of string literals:<br>" - + "<pre class=\"language-python\">a = 'abc\\ndef'\n" - + "b = \"ab'cd\"\n" - + "c = \"\"\"multiline string\"\"\"\n" - + "\n" - + "# Strings support slicing (negative index starts from the end):\n" - + "x = \"hello\"[2:4] # \"ll\"\n" - + "y = \"hello\"[1:-1] # \"ell\"\n" - + "z = \"hello\"[:4] # \"hell\"</pre>" - + "Strings are iterable and support the <code>in</code> operator. Examples:<br>" - + "<pre class=\"language-python\">\"bc\" in \"abcd\" # evaluates to True\n" - + "x = []\n" - + "for s in \"abc\":\n" - + " x += [s] # x == [\"a\", \"b\", \"c\"]</pre>\n" - + "Implicit concatenation of strings is not allowed; use the <code>+</code> " - + "operator instead.") - public static final class StringModule {} - - /** - * Skylark Dict module. - */ - @SkylarkModule(name = "dict", doc = - "A language built-in type to support dicts. " - + "Example of dict literal:<br>" - + "<pre class=\"language-python\">d = {\"a\": 2, \"b\": 5}</pre>" - + "Use brackets to access elements:<br>" - + "<pre class=\"language-python\">e = d[\"a\"] # e == 2</pre>" - + "Dicts support the <code>+</code> operator to concatenate two dicts. In case of multiple " - + "keys the second one overrides the first one. Examples:<br>" - + "<pre class=\"language-python\">" - + "d = {\"a\" : 1} + {\"b\" : 2} # d == {\"a\" : 1, \"b\" : 2}\n" - + "d += {\"c\" : 3} # d == {\"a\" : 1, \"b\" : 2, \"c\" : 3}\n" - + "d = d + {\"c\" : 5} # d == {\"a\" : 1, \"b\" : 2, \"c\" : 5}</pre>" - + "Since the language doesn't have mutable objects <code>d[\"a\"] = 5</code> automatically " - + "translates to <code>d = d + {\"a\" : 5}</code>.<br>" - + "Iterating on a dict is equivalent to iterating on its keys (in sorted order).<br>" - + "Dicts support the <code>in</code> operator, testing membership in the keyset of the dict. " - + "Example:<br>" - + "<pre class=\"language-python\">\"a\" in {\"a\" : 2, \"b\" : 5} # evaluates as True" - + "</pre>") - public static final class DictModule {} - - public static final List<BaseFunction> stringFunctions = ImmutableList.<BaseFunction>of( - count, endswith, find, index, format, join, lower, partition, replace, rfind, - rindex, rpartition, rsplit, slice, split, startswith, strip, upper); - - public static final List<BaseFunction> listPureFunctions = ImmutableList.<BaseFunction>of( - slice); - - public static final List<BaseFunction> listFunctions = ImmutableList.<BaseFunction>of( - append, extend); - - public static final List<BaseFunction> setFunctions = ImmutableList.<BaseFunction>of(union); - - public static final List<BaseFunction> dictFunctions = ImmutableList.<BaseFunction>of( - items, get, keys, values); - - private static final List<BaseFunction> pureGlobalFunctions = ImmutableList.<BaseFunction>of( - bool, int_, len, minus, repr, select, sorted, str); - - private static final List<BaseFunction> skylarkGlobalFunctions = - ImmutableList.<BaseFunction>builder() - .addAll(pureGlobalFunctions) - .add(list, struct, hasattr, getattr, set, dir, enumerate, range, type, fail, print, zip) - .build(); - - /** - * Set up a given environment for supported class methods. - */ - public static void setupMethodEnvironment(Environment env) { - env.registerFunction(Map.class, indexOperator.getName(), indexOperator); - setupMethodEnvironment(env, Map.class, dictFunctions); - env.registerFunction(String.class, indexOperator.getName(), indexOperator); - setupMethodEnvironment(env, String.class, stringFunctions); - setupMethodEnvironment(env, List.class, listPureFunctions); - setupMethodEnvironment(env, SkylarkList.class, listPureFunctions); - setupMethodEnvironment(env, SkylarkNestedSet.class, setFunctions); - if (env.isSkylarkEnabled()) { - env.registerFunction(SkylarkList.class, indexOperator.getName(), indexOperator); - setupMethodEnvironment(env, skylarkGlobalFunctions); - } else { - env.registerFunction(List.class, indexOperator.getName(), indexOperator); - env.registerFunction(ImmutableList.class, indexOperator.getName(), indexOperator); - // TODO(bazel-team): listFunctions are not allowed in Skylark extensions (use += instead). - // It is allowed in BUILD files only for backward-compatibility. - setupMethodEnvironment(env, List.class, listFunctions); - setupMethodEnvironment(env, pureGlobalFunctions); - } - } - - private static void setupMethodEnvironment( - Environment env, Class<?> nameSpace, Iterable<BaseFunction> functions) { - for (BaseFunction function : functions) { - env.registerFunction(nameSpace, function.getName(), function); - } - } - - private static void setupMethodEnvironment(Environment env, Iterable<BaseFunction> functions) { - for (BaseFunction function : functions) { - env.update(function.getName(), function); - } - } - - /** - * Collect global functions for the validation environment. - */ - public static void setupValidationEnvironment(Set<String> builtIn) { - for (BaseFunction function : skylarkGlobalFunctions) { - builtIn.add(function.getName()); - } - } - - static { - SkylarkSignatureProcessor.configureSkylarkFunctions(MethodLibrary.class); - } -} diff --git a/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java b/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java index 76f0bc0d2d..062754dc4a 100644 --- a/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java +++ b/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java @@ -44,6 +44,7 @@ import com.google.devtools.build.lib.syntax.FunctionSignature; import com.google.devtools.build.lib.syntax.GlobList; import com.google.devtools.build.lib.syntax.Identifier; import com.google.devtools.build.lib.syntax.Label; +import com.google.devtools.build.lib.syntax.MethodLibrary; import com.google.devtools.build.lib.syntax.ParserInputSource; import com.google.devtools.build.lib.syntax.SkylarkEnvironment; import com.google.devtools.build.lib.syntax.SkylarkSignature; diff --git a/src/main/java/com/google/devtools/build/lib/packages/WorkspaceFactory.java b/src/main/java/com/google/devtools/build/lib/packages/WorkspaceFactory.java index 91a71106ae..d72d8aa9d0 100644 --- a/src/main/java/com/google/devtools/build/lib/packages/WorkspaceFactory.java +++ b/src/main/java/com/google/devtools/build/lib/packages/WorkspaceFactory.java @@ -29,6 +29,7 @@ import com.google.devtools.build.lib.syntax.EvalException; import com.google.devtools.build.lib.syntax.FuncallExpression; import com.google.devtools.build.lib.syntax.FunctionSignature; import com.google.devtools.build.lib.syntax.Label; +import com.google.devtools.build.lib.syntax.MethodLibrary; import com.google.devtools.build.lib.syntax.ParserInputSource; import java.io.File; |