diff options
author | 2015-12-11 21:54:43 +0000 | |
---|---|---|
committer | 2015-12-13 18:27:40 +0000 | |
commit | 5e8752b64acf3b5d5ae0a8929f20119e1281628a (patch) | |
tree | 3984fb9e7129606ea5090b8bb2dd7aeb8572bc02 /src | |
parent | 1812967ba20266cae559bf117f0efc8e32d43fcf (diff) |
Skylark: implemented min() and max().
--
MOS_MIGRATED_REVID=110025690
Diffstat (limited to 'src')
3 files changed, 171 insertions, 8 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/EvalUtils.java b/src/main/java/com/google/devtools/build/lib/syntax/EvalUtils.java index a0f2e5b52a..e63b850f69 100644 --- a/src/main/java/com/google/devtools/build/lib/syntax/EvalUtils.java +++ b/src/main/java/com/google/devtools/build/lib/syntax/EvalUtils.java @@ -28,7 +28,6 @@ import com.google.devtools.build.lib.vfs.PathFragment; import net.bytebuddy.implementation.bytecode.StackManipulation; import java.util.Collection; -import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Set; @@ -57,7 +56,7 @@ public final class EvalUtils { * <p> It may throw an unchecked exception ComparisonException that should be wrapped in * an EvalException. */ - public static final Comparator<Object> SKYLARK_COMPARATOR = new Comparator<Object>() { + public static final Ordering<Object> SKYLARK_COMPARATOR = new Ordering<Object>() { private int compareLists(SkylarkList o1, SkylarkList o2) { for (int i = 0; i < Math.min(o1.size(), o2.size()); i++) { int cmp = compare(o1.get(i), o2.get(i)); @@ -390,7 +389,7 @@ public final class EvalUtils { // For dictionaries we iterate through the keys only // For determinism, we sort the keys. try { - return Ordering.from(SKYLARK_COMPARATOR).sortedCopy(dict.keySet()); + return SKYLARK_COMPARATOR.sortedCopy(dict.keySet()); } catch (ComparisonException e) { throw new EvalException(loc, e); } diff --git a/src/main/java/com/google/devtools/build/lib/syntax/MethodLibrary.java b/src/main/java/com/google/devtools/build/lib/syntax/MethodLibrary.java index 2f17084a50..3d8c7feb8c 100644 --- a/src/main/java/com/google/devtools/build/lib/syntax/MethodLibrary.java +++ b/src/main/java/com/google/devtools/build/lib/syntax/MethodLibrary.java @@ -35,6 +35,7 @@ import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.NoSuchElementException; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; @@ -850,6 +851,64 @@ public class MethodLibrary { return list.subList(left, right); } + @SkylarkSignature( + name = "min", + returnType = Object.class, + doc = + "Returns the smallest one of all given arguments. " + + "If only one argument is provided, it must be a non-empty iterable.", + extraPositionals = { + @Param(name = "args", type = SkylarkList.class, doc = "The elements to be checked.") + }, + useLocation = true + ) + private static BuiltinFunction min = new BuiltinFunction("min") { + @SuppressWarnings("unused") // Accessed via Reflection. + public Object invoke(SkylarkList args, Location loc) throws EvalException { + return findExtreme(args, EvalUtils.SKYLARK_COMPARATOR.reverse(), loc); + } + }; + + @SkylarkSignature( + name = "max", + returnType = Object.class, + doc = + "Returns the largest one of all given arguments. " + + "If only one argument is provided, it must be a non-empty iterable.", + extraPositionals = { + @Param(name = "args", type = SkylarkList.class, doc = "The elements to be checked.") + }, + useLocation = true + ) + private static BuiltinFunction max = new BuiltinFunction("max") { + @SuppressWarnings("unused") // Accessed via Reflection. + public Object invoke(SkylarkList args, Location loc) throws EvalException { + return findExtreme(args, EvalUtils.SKYLARK_COMPARATOR, loc); + } + }; + + /** + * Returns the maximum element from this list, as determined by maxOrdering. + */ + private static Object findExtreme(SkylarkList args, Ordering<Object> maxOrdering, Location loc) + throws EvalException { + // Args can either be a list of elements or a list whose first element is a non-empty iterable + // of elements. + try { + return maxOrdering.max(getIterable(args, loc)); + } catch (NoSuchElementException ex) { + throw new EvalException(loc, "Expected at least one argument"); + } + } + + /** + * This method returns the first element of the list, if that particular element is an + * Iterable<?>. Otherwise, it will return the entire list. + */ + private static Iterable<?> getIterable(SkylarkList list, Location loc) throws EvalException { + return (list.size() == 1) ? EvalUtils.toIterable(list.get(0), loc) : list; + } + // supported list methods @SkylarkSignature( name = "sorted", @@ -867,8 +926,7 @@ public class MethodLibrary { throws EvalException, ConversionException { try { return new MutableList( - Ordering.from(EvalUtils.SKYLARK_COMPARATOR).sortedCopy( - EvalUtils.toCollection(self, loc)), + EvalUtils.SKYLARK_COMPARATOR.sortedCopy(EvalUtils.toCollection(self, loc)), env); } catch (EvalUtils.ComparisonException e) { throw new EvalException(loc, e); @@ -1630,9 +1688,9 @@ public class MethodLibrary { static final List<BaseFunction> skylarkGlobalFunctions = ImmutableList.<BaseFunction>builder() - .addAll(buildGlobalFunctions) - .add(dir, fail, getattr, hasattr, print, set, struct, type) - .build(); + .addAll(buildGlobalFunctions) + .add(dir, fail, getattr, hasattr, max, min, print, set, struct, type) + .build(); /** diff --git a/src/test/java/com/google/devtools/build/lib/syntax/MethodLibraryTest.java b/src/test/java/com/google/devtools/build/lib/syntax/MethodLibraryTest.java index 76f46bfa35..e76471cf6d 100644 --- a/src/test/java/com/google/devtools/build/lib/syntax/MethodLibraryTest.java +++ b/src/test/java/com/google/devtools/build/lib/syntax/MethodLibraryTest.java @@ -40,6 +40,112 @@ public class MethodLibraryTest extends EvaluationTestCase { } @Test + public void testMinWithInvalidArgs() throws Exception { + new SkylarkTest() + .testIfExactError("type 'int' is not iterable", "min(1)") + .testIfExactError("Expected at least one argument", "min([])"); + } + + @Test + public void testMinWithString() throws Exception { + new SkylarkTest() + .testStatement("min('abcdefxyz')", "a") + .testStatement("min('test', 'xyz')", "test"); + } + + @Test + public void testMinWithList() throws Exception { + new SkylarkTest() + .testEval("min([4, 5], [1])", "[1]") + .testEval("min([1, 2], [3])", "[1, 2]") + .testEval("min([1, 5], [1, 6], [2, 4], [0, 6])", "[0, 6]") + .testStatement("min([-1])", -1) + .testStatement("min([5, 2, 3])", 2); + } + + @Test + public void testMinWithDict() throws Exception { + new SkylarkTest().testStatement("min({1: 2, -1 : 3})", -1).testStatement("min({2: None})", 2); + } + + @Test + public void testMinWithSet() throws Exception { + new SkylarkTest().testStatement("min(set([-1]))", -1).testStatement("min(set([5, 2, 3]))", 2); + } + + @Test + public void testMinWithPositionalArguments() throws Exception { + new SkylarkTest().testStatement("min(-1, 2)", -1).testStatement("min(5, 2, 3)", 2); + } + + @Test + public void testMinWithSameValues() throws Exception { + new SkylarkTest() + .testStatement("min(1, 1, 1, 1, 1, 1)", 1) + .testStatement("min([1, 1, 1, 1, 1, 1])", 1); + } + + @Test + public void testMinWithDifferentTypes() throws Exception { + new SkylarkTest() + .testStatement("min(1, '2', True)", true) + .testStatement("min([1, '2', True])", true) + .testStatement("min(None, 1, 'test')", Runtime.NONE); + } + + @Test + public void testMaxWithInvalidArgs() throws Exception { + new SkylarkTest() + .testIfExactError("type 'int' is not iterable", "max(1)") + .testIfExactError("Expected at least one argument", "max([])"); + } + + @Test + public void testMaxWithString() throws Exception { + new SkylarkTest() + .testStatement("max('abcdefxyz')", "z") + .testStatement("max('test', 'xyz')", "xyz"); + } + + @Test + public void testMaxWithList() throws Exception { + new SkylarkTest() + .testEval("max([1, 2], [5])", "[5]") + .testStatement("max([-1])", -1) + .testStatement("max([5, 2, 3])", 5); + } + + @Test + public void testMaxWithDict() throws Exception { + new SkylarkTest().testStatement("max({1: 2, -1 : 3})", 1).testStatement("max({2: None})", 2); + } + + @Test + public void testMaxWithSet() throws Exception { + new SkylarkTest().testStatement("max(set([-1]))", -1).testStatement("max(set([5, 2, 3]))", 5); + } + + @Test + public void testMaxWithPositionalArguments() throws Exception { + new SkylarkTest().testStatement("max(-1, 2)", 2).testStatement("max(5, 2, 3)", 5); + } + + @Test + public void testMaxWithSameValues() throws Exception { + new SkylarkTest() + .testStatement("max(1, 1, 1, 1, 1, 1)", 1) + .testStatement("max([1, 1, 1, 1, 1, 1])", 1); + } + + @Test + public void testMaxWithDifferentTypes() throws Exception { + new SkylarkTest() + .testStatement("max(1, '2', True)", "2") + .testStatement("max([1, '2', True])", "2") + .testStatement("max(None, 1, 'test')", "test"); + } + + @Test public void testSplitLines_EmptyLine() throws Exception { new SkylarkTest().testEval("''.splitlines()", "[]").testEval("'\\n'.splitlines()", "['']"); } |