use std::{cmp::Ordering, mem};
use crate::{
args::{ArgValues, FromArgs},
bytecode::VM,
defer_drop, defer_drop_mut,
exception_private::{ExcType, RunError, RunResult, SimpleException},
heap::HeapGuard,
resource::ResourceTracker,
types::{CmpOrder, MontyIter, PyTrait},
value::Value,
};
pub fn builtin_min(vm: &mut VM<'_, impl ResourceTracker>, 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<'_, impl ResourceTracker>, 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<'_, impl ResourceTracker>,
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_heap(vm);
None
}
_ => Some(key),
};
defer_drop!(key_fn, vm);
let mut default_guard = HeapGuard::new(default, vm);
let (default_value, vm) = default_guard.as_parts_mut();
let mut args_guard = HeapGuard::new(args, vm);
let (args, vm) = args_guard.as_parts_mut();
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 = MontyIter::new(first_arg, vm)?;
defer_drop_mut!(iter, vm);
let Some(result) = iter.for_next(vm)? else {
if let Some(default) = default_value.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 = HeapGuard::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)?;
let mut result_key_guard = HeapGuard::new(result_key, vm);
{
let (result_key, vm) = result_key_guard.as_parts_mut();
while let Some(item) = iter.for_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);
}
}
}
let result_key = result_key_guard.into_inner();
result_key.drop_with_heap(vm);
}
Ok(result_guard.into_inner())
} else {
let mut result_guard = HeapGuard::new(result, vm);
let (result, vm) = result_guard.as_parts_mut();
while let Some(item) = iter.for_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_value.is_some() {
first_arg.drop_with_heap(vm);
return Err(default_with_multiple_args(func_name));
}
if let Some(key_fn) = key_fn {
let mut result_guard = HeapGuard::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)?;
let mut result_key_guard = HeapGuard::new(result_key, vm);
{
let (result_key, vm) = result_key_guard.as_parts_mut();
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);
}
}
}
let result_key = result_key_guard.into_inner();
result_key.drop_with_heap(vm);
}
Ok(result_guard.into_inner())
} else {
let mut result_guard = HeapGuard::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<'_, impl ResourceTracker>,
) -> 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<'_, impl ResourceTracker>,
) -> 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<'_, impl ResourceTracker>) -> 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)
}