use std::{cmp::Ordering, mem};
use crate::{
args::{ArgValues, FromArgs},
bytecode::VM,
defer_drop, defer_drop_mut,
exception_private::{ExcType, ExcTypeExt, RunError, RunResult, SimpleException},
heap::DropGuard,
types::{CmpOrder, PyTrait},
value::Value,
};
pub fn builtin_min(vm: &mut VM<'_>, args: ArgValues) -> RunResult<Value> {
let MinArgs { args, key, default } = MinArgs::from_args(args, vm)?;
run_min_max(vm, args, key, default, true)
}
pub fn builtin_max(vm: &mut VM<'_>, args: ArgValues) -> RunResult<Value> {
let MaxArgs { args, key, default } = MaxArgs::from_args(args, vm)?;
run_min_max(vm, args, key, default, false)
}
fn run_min_max(
vm: &mut VM<'_>,
args: Vec<Value>,
key: Value,
default: Option<Value>,
is_min: bool,
) -> RunResult<Value> {
let func_name = if is_min { "min" } else { "max" };
let key_context = if is_min {
"min() key argument"
} else {
"max() key argument"
};
let key_fn = match key {
Value::None => {
key.drop_with(vm);
None
}
_ => Some(key),
};
defer_drop!(key_fn, vm);
defer_drop_mut!(default, vm);
defer_drop_mut!(args, vm);
if args.is_empty() {
return Err(SimpleException::new_msg(
ExcType::TypeError,
format!("{func_name} expected at least 1 argument, got 0"),
)
.into());
}
let first_arg = args.remove(0);
if args.is_empty() {
let iter = first_arg.into_py_iter(vm)?;
defer_drop!(iter, vm);
let mut iter = iter.read(vm);
let Some(result) = iter.py_next(vm)? else {
if let Some(default) = default.take() {
return Ok(default);
}
return Err(SimpleException::new_msg(
ExcType::ValueError,
format!("{func_name}() iterable argument is empty"),
)
.into());
};
if let Some(key_fn) = key_fn {
let mut result_guard = DropGuard::new(result, vm);
{
let (result, vm) = result_guard.as_parts_mut();
let result_key = evaluate_key(result.clone_with_heap(vm), key_fn, key_context, vm)?;
defer_drop_mut!(result_key, vm);
while let Some(item) = iter.py_next(vm)? {
defer_drop_mut!(item, vm);
let item_key = evaluate_key(item.clone_with_heap(vm), key_fn, key_context, vm)?;
defer_drop_mut!(item_key, vm);
if candidate_wins(result_key, item_key, is_min, vm)? {
mem::swap(result, item);
mem::swap(result_key, item_key);
}
}
}
Ok(result_guard.into_inner())
} else {
let mut result_guard = DropGuard::new(result, vm);
let (result, vm) = result_guard.as_parts_mut();
while let Some(item) = iter.py_next(vm)? {
defer_drop_mut!(item, vm);
if candidate_wins(result, item, is_min, vm)? {
mem::swap(result, item);
}
}
Ok(result_guard.into_inner())
}
} else {
if default.is_some() {
first_arg.drop_with(vm);
return Err(default_with_multiple_args(func_name));
}
if let Some(key_fn) = key_fn {
let mut result_guard = DropGuard::new(first_arg, vm);
{
let (result, vm) = result_guard.as_parts_mut();
let result_key = evaluate_key(result.clone_with_heap(vm), key_fn, key_context, vm)?;
defer_drop_mut!(result_key, vm);
for item in args.drain(..) {
defer_drop_mut!(item, vm);
let item_key = evaluate_key(item.clone_with_heap(vm), key_fn, key_context, vm)?;
defer_drop_mut!(item_key, vm);
if candidate_wins(result_key, item_key, is_min, vm)? {
mem::swap(result, item);
mem::swap(result_key, item_key);
}
}
}
Ok(result_guard.into_inner())
} else {
let mut result_guard = DropGuard::new(first_arg, vm);
let (result, vm) = result_guard.as_parts_mut();
for item in args.drain(..) {
defer_drop_mut!(item, vm);
if candidate_wins(result, item, is_min, vm)? {
mem::swap(result, item);
}
}
Ok(result_guard.into_inner())
}
}
}
#[derive(FromArgs)]
#[from_args(name = "min")]
struct MinArgs {
#[from_args(varargs)]
args: Vec<Value>,
#[from_args(default = Value::None)]
key: Value,
#[from_args(default)]
default: Option<Value>,
}
#[derive(FromArgs)]
#[from_args(name = "max")]
struct MaxArgs {
#[from_args(varargs)]
args: Vec<Value>,
#[from_args(default = Value::None)]
key: Value,
#[from_args(default)]
default: Option<Value>,
}
fn evaluate_key(item: Value, key_fn: &Value, key_context: &'static str, vm: &mut VM<'_>) -> RunResult<Value> {
vm.evaluate_function(key_context, key_fn, ArgValues::One(item))
}
fn candidate_wins(current: &Value, candidate: &Value, is_min: bool, vm: &mut VM<'_>) -> RunResult<bool> {
let ordering = match candidate.py_cmp(current, vm)? {
CmpOrder::Ordered(ordering) => ordering,
CmpOrder::Unordered => return Ok(false),
CmpOrder::Incomparable => return Err(ord_not_supported(candidate, current, is_min, vm)),
};
Ok((is_min && ordering == Ordering::Less) || (!is_min && ordering == Ordering::Greater))
}
#[cold]
fn default_with_multiple_args(func_name: &str) -> RunError {
SimpleException::new_msg(
ExcType::TypeError,
format!("Cannot specify a default for {func_name}() with multiple positional arguments"),
)
.into()
}
#[cold]
fn ord_not_supported(left: &Value, right: &Value, is_min: bool, vm: &VM<'_>) -> RunError {
let left_type = left.py_type_name(vm);
let right_type = right.py_type_name(vm);
let operator = if is_min { "<" } else { ">" };
ExcType::type_error_ordering(operator, &left_type, &right_type)
}