starlark/stdlib/funcs/
min_max.rs

1/*
2 * Copyright 2018 The Starlark in Rust Authors.
3 * Copyright (c) Facebook, Inc. and its affiliates.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *     https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18use std::cmp::Ordering;
19
20use starlark_derive::starlark_module;
21
22use crate as starlark;
23use crate::environment::GlobalsBuilder;
24use crate::eval::Evaluator;
25use crate::values::tuple::UnpackTuple;
26use crate::values::Value;
27
28fn min_max_iter<'v>(
29    mut it: impl Iterator<Item = Value<'v>>,
30    key: Option<Value<'v>>,
31    eval: &mut Evaluator<'v, '_, '_>,
32    // Select min on true, max on false.
33    min: bool,
34) -> crate::Result<Value<'v>> {
35    let mut max = match it.next() {
36        Some(x) => x,
37        None => {
38            return Err(anyhow::anyhow!(
39                "Argument is an empty iterable, max() expect a non empty iterable"
40            )
41            .into());
42        }
43    };
44    let update_max_ordering = if min {
45        Ordering::Greater
46    } else {
47        Ordering::Less
48    };
49    match key {
50        None => {
51            for i in it {
52                if max.compare(i)? == update_max_ordering {
53                    max = i;
54                }
55            }
56        }
57        Some(key) => {
58            let mut cached = key.invoke_pos(&[max], eval)?;
59            for i in it {
60                let keyi = key.invoke_pos(&[i], eval)?;
61                if cached.compare(keyi)? == update_max_ordering {
62                    max = i;
63                    cached = keyi;
64                }
65            }
66        }
67    };
68    Ok(max)
69}
70
71/// Common implementation of `min` and `max`.
72fn min_max<'v>(
73    mut args: UnpackTuple<Value<'v>>,
74    key: Option<Value<'v>>,
75    eval: &mut Evaluator<'v, '_, '_>,
76    // Select min on true, max on false.
77    min: bool,
78) -> crate::Result<Value<'v>> {
79    if args.items.len() == 1 {
80        let it = args.items.swap_remove(0).iterate(eval.heap())?;
81        min_max_iter(it, key, eval, min)
82    } else {
83        min_max_iter(args.items.into_iter(), key, eval, min)
84    }
85}
86
87#[starlark_module]
88pub(crate) fn register_min_max(globals: &mut GlobalsBuilder) {
89    /// [max](
90    /// https://github.com/bazelbuild/starlark/blob/master/spec.md#max
91    /// ): returns the maximum of a sequence.
92    ///
93    /// `max(x)` returns the greatest element in the iterable sequence x.
94    ///
95    /// It is an error if any element does not support ordered comparison,
96    /// or if the sequence is empty.
97    ///
98    /// The optional named parameter `key` specifies a function to be applied
99    /// to each element prior to comparison.
100    ///
101    /// ```
102    /// # starlark::assert::all_true(r#"
103    /// max([3, 1, 4, 1, 5, 9])               == 9
104    /// max("two", "three", "four")           == "two"    # the lexicographically greatest
105    /// max("two", "three", "four", key=len)  == "three"  # the longest
106    /// # "#);
107    /// ```
108    #[starlark(speculative_exec_safe)]
109    fn max<'v>(
110        #[starlark(args)] args: UnpackTuple<Value<'v>>,
111        key: Option<Value<'v>>,
112        eval: &mut Evaluator<'v, '_, '_>,
113    ) -> starlark::Result<Value<'v>> {
114        min_max(args, key, eval, false)
115    }
116
117    /// [min](
118    /// https://github.com/bazelbuild/starlark/blob/master/spec.md#min
119    /// ): returns the minimum of a sequence.
120    ///
121    /// `min(x)` returns the least element in the iterable sequence x.
122    ///
123    /// It is an error if any element does not support ordered comparison,
124    /// or if the sequence is empty.
125    ///
126    /// ```
127    /// # starlark::assert::all_true(r#"
128    /// min([3, 1, 4, 1, 5, 9])                 == 1
129    /// min("two", "three", "four")             == "four"  # the lexicographically least
130    /// min("two", "three", "four", key=len)    == "two"   # the shortest
131    /// # "#);
132    /// ```
133    #[starlark(speculative_exec_safe)]
134    fn min<'v>(
135        #[starlark(args)] args: UnpackTuple<Value<'v>>,
136        key: Option<Value<'v>>,
137        eval: &mut Evaluator<'v, '_, '_>,
138    ) -> starlark::Result<Value<'v>> {
139        min_max(args, key, eval, true)
140    }
141}