use std::cmp::Ordering;
use crate::{
args::{ArgValues, FromArgs, LaxBool},
bytecode::VM,
defer_drop, defer_drop_mut,
exception_private::{ExcType, ExcTypeExt, RunError, RunResult},
types::{CmpOrder, PyTrait},
value::Value,
};
#[derive(FromArgs)]
#[from_args(name = "sort")]
struct ListSortArgs {
#[from_args(kw_only, default)]
key: Option<Value>,
#[from_args(kw_only, default = LaxBool::new(false))]
reverse: LaxBool,
}
pub fn parse_and_sort(items: &mut [Value], args: ArgValues, vm: &mut VM<'_>) -> RunResult<()> {
let ListSortArgs { key, reverse } = ListSortArgs::from_args(args, vm)?;
let key_fn = match key {
Some(v) if matches!(v, Value::None) => {
v.drop_with(vm);
None
}
other => other,
};
defer_drop!(key_fn, vm);
sort_values(items, key_fn.as_ref(), reverse.bool(), vm)
}
pub fn sort_values(values: &mut [Value], key_fn: Option<&Value>, reverse: bool, vm: &mut VM<'_>) -> RunResult<()> {
if let Some(f) = key_fn {
let mut indices = (0..values.len()).collect::<Vec<_>>();
let keys: Vec<Value> = Vec::with_capacity(values.len());
defer_drop_mut!(keys, vm);
for item in values.iter() {
let item = item.clone_with_heap(vm);
keys.push(vm.evaluate_function("sorted() key argument", f, ArgValues::One(item))?);
}
sort_indices(&mut indices, keys, reverse, vm)?;
apply_permutation(values, &mut indices);
Ok(())
} else {
let mut sort_result: RunResult<()> = Ok(());
values.sort_by(|a, b| compare_values(a, b, reverse, &mut sort_result, vm));
sort_result
}
}
pub fn sort_indices(indices: &mut [usize], values: &[Value], reverse: bool, vm: &mut VM<'_>) -> Result<(), RunError> {
let mut sort_result: RunResult<()> = Ok(());
indices.sort_by(|&a, &b| compare_values(&values[a], &values[b], reverse, &mut sort_result, vm));
sort_result
}
pub fn apply_permutation<T>(items: &mut [T], indices: &mut [usize]) {
for i in 0..items.len() {
if indices[i] == i {
continue;
}
let mut current = i;
loop {
let target = indices[current];
indices[current] = current;
if target == i {
break;
}
items.swap(current, target);
current = target;
}
}
}
fn compare_values(a: &Value, b: &Value, reverse: bool, sort_result: &mut RunResult<()>, vm: &mut VM<'_>) -> Ordering {
if sort_result.is_err() {
return Ordering::Equal;
}
if let Err(e) = vm.heap.check_time() {
*sort_result = Err(e.into());
return Ordering::Equal;
}
let err = match a.py_cmp(b, vm) {
Ok(CmpOrder::Ordered(ord)) => return if reverse { ord.reverse() } else { ord },
Ok(CmpOrder::Unordered) => return Ordering::Equal,
Ok(CmpOrder::Incomparable) => ExcType::type_error(format!(
"'<' not supported between instances of '{}' and '{}'",
a.py_type_name(vm),
b.py_type_name(vm)
)),
Err(e) => e,
};
*sort_result = Err(err);
Ordering::Equal
}