Skylark: implemented min() and max().

--
MOS_MIGRATED_REVID=110025690
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 a0f2e5b..e63b850 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 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 @@
    * <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 @@
       // 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 2f17084..3d8c7fe 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.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 @@
     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 @@
             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 @@
 
   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 76f46bf..e76471c 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 @@
   }
 
   @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()", "['']");
   }