use std::{
collections::hash_map::DefaultHasher,
fmt,
fmt::Write,
hash::{Hash, Hasher},
mem,
};
use super::LazyHeapSet;
use crate::{
args::ArgValues,
bytecode::{CallResult, VM},
defer_drop,
exception_private::{ExcType, RunResult},
hash::HashValue,
heap::{HeapData, HeapId, HeapItem, HeapRead, HeapReadOutput},
intern::StaticStrings,
resource::ResourceTracker,
types::{PyTrait, Type},
value::{EitherStr, Value},
};
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub(crate) struct Slice {
pub start: Option<i64>,
pub stop: Option<i64>,
pub step: Option<i64>,
}
impl Slice {
#[must_use]
pub fn new(start: Option<i64>, stop: Option<i64>, step: Option<i64>) -> Self {
Self { start, stop, step }
}
pub fn init(vm: &mut VM<'_, impl ResourceTracker>, args: ArgValues) -> RunResult<Value> {
let heap = &mut *vm.heap;
let pos_args = args.into_pos_only("slice", heap)?;
defer_drop!(pos_args, heap);
let slice = match pos_args.as_slice() {
[] => return Err(ExcType::type_error_at_least("slice", 1, 0)),
[first_arg] => {
let stop = value_to_option_i64(first_arg)?;
Self::new(None, stop, None)
}
[first_arg, second_arg] => {
let start = value_to_option_i64(first_arg)?;
let stop = value_to_option_i64(second_arg)?;
Self::new(start, stop, None)
}
[first_arg, second_arg, third_arg] => {
let start = value_to_option_i64(first_arg)?;
let stop = value_to_option_i64(second_arg)?;
let step = value_to_option_i64(third_arg)?;
Self::new(start, stop, step)
}
_ => return Err(ExcType::type_error_at_most("slice", 3, pos_args.len())),
};
Ok(Value::Ref(heap.allocate(HeapData::Slice(slice))?))
}
pub fn indices(&self, length: usize) -> RunResult<(i64, i64, i64)> {
let step = self.step.unwrap_or(1);
if step == 0 {
return Err(ExcType::value_error_slice_step_zero());
}
let len = i64::try_from(length).unwrap_or(i64::MAX);
if step > 0 {
let default_start = 0;
let default_stop = len;
let start = self.start.map_or(default_start, |s| normalize_index(s, len, 0, len));
let stop = self.stop.map_or(default_stop, |s| normalize_index(s, len, 0, len));
Ok((start, stop, step))
} else {
let default_start = len - 1;
let default_stop = -1;
let start = self
.start
.map_or(default_start, |s| normalize_index(s, len, -1, len - 1));
let stop = self.stop.map_or(default_stop, |s| normalize_index(s, len, -1, len - 1));
Ok((start, stop, step))
}
}
}
pub(crate) fn value_to_option_i64(value: &Value) -> RunResult<Option<i64>> {
match value {
Value::None => Ok(None),
Value::Int(i) => Ok(Some(*i)),
Value::Bool(b) => Ok(Some(i64::from(*b))),
_ => Err(ExcType::type_error_slice_indices()),
}
}
fn normalize_index(index: i64, length: i64, lower: i64, upper: i64) -> i64 {
let normalized = if index < 0 { index + length } else { index };
normalized.clamp(lower, upper)
}
impl<'h> PyTrait<'h> for HeapRead<'h, Slice> {
fn py_type(&self, _vm: &VM<'h, impl ResourceTracker>) -> Type {
Type::Slice
}
fn py_len(&self, _vm: &VM<'h, impl ResourceTracker>) -> Option<usize> {
None
}
fn py_eq_impl(&self, other: &Value, vm: &mut VM<'h, impl ResourceTracker>) -> RunResult<Option<bool>> {
let Some(HeapReadOutput::Slice(other)) = other.read_heap(vm) else {
return Ok(None);
};
let a = self.get(vm.heap);
let b = other.get(vm.heap);
Ok(Some(a.start == b.start && a.stop == b.stop && a.step == b.step))
}
fn py_hash(&self, _self_id: HeapId, vm: &mut VM<'h, impl ResourceTracker>) -> RunResult<Option<HashValue>> {
let mut hasher = DefaultHasher::new();
self.get(vm.heap).hash(&mut hasher);
Ok(Some(HashValue::new(hasher.finish())))
}
fn py_bool(&self, _vm: &mut VM<'h, impl ResourceTracker>) -> bool {
true
}
fn py_repr_fmt(
&self,
f: &mut impl Write,
vm: &mut VM<'h, impl ResourceTracker>,
_heap_ids: &mut LazyHeapSet,
) -> RunResult<()> {
f.write_str("slice(")?;
format_option_i64(f, self.get(vm.heap).start)?;
f.write_str(", ")?;
format_option_i64(f, self.get(vm.heap).stop)?;
f.write_str(", ")?;
format_option_i64(f, self.get(vm.heap).step)?;
Ok(f.write_char(')')?)
}
fn py_getattr(&self, attr: &EitherStr, vm: &mut VM<'h, impl ResourceTracker>) -> RunResult<Option<CallResult>> {
let this = self.get(vm.heap);
if let Some(ss) = attr.static_string() {
return match ss {
StaticStrings::Start => Ok(Some(CallResult::Value(option_i64_to_value(this.start)))),
StaticStrings::Stop => Ok(Some(CallResult::Value(option_i64_to_value(this.stop)))),
StaticStrings::Step => Ok(Some(CallResult::Value(option_i64_to_value(this.step)))),
_ => Ok(None),
};
}
match attr.as_str(vm.interns) {
"start" => Ok(Some(CallResult::Value(option_i64_to_value(this.start)))),
"stop" => Ok(Some(CallResult::Value(option_i64_to_value(this.stop)))),
"step" => Ok(Some(CallResult::Value(option_i64_to_value(this.step)))),
_ => Ok(None),
}
}
}
impl HeapItem for Slice {
fn py_estimate_size(&self) -> usize {
mem::size_of::<Self>()
}
fn py_dec_ref_ids(&mut self, _stack: &mut Vec<HeapId>) {
}
}
pub(crate) fn option_i64_to_value(opt: Option<i64>) -> Value {
match opt {
Some(i) => Value::Int(i),
None => Value::None,
}
}
fn format_option_i64(f: &mut impl Write, value: Option<i64>) -> fmt::Result {
match value {
Some(i) => write!(f, "{i}"),
None => f.write_str("None"),
}
}
pub(crate) fn slice_collect_iterator<Iter: DoubleEndedIterator + Clone, U, T: FromIterator<U>>(
vm: &VM<'_, impl ResourceTracker>,
slice: &Slice,
iter: Iter,
collect_map: impl Fn(Iter::Item) -> U,
) -> RunResult<T> {
let length = iter.clone().count();
let (start, stop, step) = slice.indices(length)?;
let final_collect_op = |item| -> RunResult<U> {
vm.heap.check_time()?;
Ok(collect_map(item))
};
if step > 0 {
let step: usize = step.try_into().unwrap_or(usize::MAX);
let start = start
.try_into()
.expect("slice.indices() guarantees start > 0 for step > 0");
let stop = stop
.try_into()
.expect("slice.indices() guarantees stop > 0 for step > 0");
iter.take(stop)
.skip(start)
.step_by(step)
.map(final_collect_op)
.collect()
} else {
let step: usize = step.unsigned_abs().try_into().unwrap_or(usize::MAX);
let normalized_start = length.saturating_sub(start.saturating_add(1).try_into().unwrap_or(usize::MAX));
let normalized_stop = length.saturating_sub(stop.saturating_add(1).try_into().unwrap_or(usize::MAX));
iter.rev()
.take(normalized_stop)
.skip(normalized_start)
.step_by(step)
.map(final_collect_op)
.collect()
}
}
pub(crate) fn normalize_sequence_index(index: i64, len: usize) -> usize {
if index < 0 {
let abs_index = index.unsigned_abs().try_into().unwrap_or(usize::MAX);
len.saturating_sub(abs_index)
} else {
usize::try_from(index).unwrap_or(len).min(len)
}
}