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}