use std::mem;
use crate::{
args::ArgValues,
bytecode::VM,
exception_private::{ExcType, RunResult},
heap::{ContainsHeap, DropWithHeap, Heap, HeapData, HeapGuard, HeapId, HeapItem, HeapRead, HeapReadOutput},
intern::{BytesId, Interns},
resource::{ResourceError, ResourceTracker, check_estimated_size},
types::{PyTrait, Range, dict_view::DictView, str::allocate_char},
value::{VALUE_SIZE, Value},
};
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct MontyIter {
index: usize,
iter_value: IterValue,
value: Value,
}
impl MontyIter {
pub fn init(vm: &mut VM<'_, impl ResourceTracker>, args: ArgValues) -> RunResult<Value> {
let (iterable, sentinel) = args.get_one_two_args("iter", vm.heap)?;
if let Some(s) = sentinel {
iterable.drop_with_heap(vm);
s.drop_with_heap(vm);
return Err(ExcType::type_error("iter(callable, sentinel) is not yet supported"));
}
if let Value::Ref(id) = &iterable
&& matches!(vm.heap.get(*id), HeapData::Iter(_))
{
return Ok(iterable);
}
let iter = Self::new(iterable, vm)?;
let id = vm.heap.allocate(HeapData::Iter(iter))?;
Ok(Value::Ref(id))
}
pub fn new(mut value: Value, vm: &mut VM<'_, impl ResourceTracker>) -> RunResult<Self> {
if let Some(iter_value) = IterValue::new(&value, vm) {
if matches!(iter_value, IterValue::Range { .. } | IterValue::IterStr { .. }) {
value.drop_with_heap(vm);
value = Value::None;
}
Ok(Self {
index: 0,
iter_value,
value,
})
} else {
let err = ExcType::type_error_not_iterable(&value.py_type_name(vm));
value.drop_with_heap(vm);
Err(err)
}
}
pub fn drop_with_heap(self, heap: &mut impl ContainsHeap) {
self.value.drop_with_heap(heap);
}
pub fn py_dec_ref_ids(&mut self, stack: &mut Vec<HeapId>) {
self.value.py_dec_ref_ids(stack);
}
pub fn value(&self) -> &Value {
&self.value
}
pub fn for_next(&mut self, vm: &mut VM<'_, impl ResourceTracker>) -> RunResult<Option<Value>> {
vm.heap.check_time()?;
match &mut self.iter_value {
IterValue::Range { next, step, len } => {
if self.index >= *len {
return Ok(None);
}
let value = *next;
*next += *step;
self.index += 1;
Ok(Some(Value::Int(value)))
}
IterValue::IterStr {
string,
byte_offset,
len,
} => {
if self.index >= *len {
Ok(None)
} else {
let c = string[*byte_offset..]
.chars()
.next()
.expect("index < len implies char exists");
*byte_offset += c.len_utf8();
self.index += 1;
Ok(Some(allocate_char(c, vm.heap)?))
}
}
IterValue::InternBytes { bytes_id, len } => {
if self.index >= *len {
return Ok(None);
}
let i = self.index;
self.index += 1;
let bytes = vm.interns.get_bytes(*bytes_id);
Ok(Some(Value::Int(i64::from(bytes[i]))))
}
IterValue::HeapRef {
heap_id,
len,
checks_mutation,
} => {
if let Some(l) = len
&& self.index >= *l
{
return Ok(None);
}
let i = self.index;
let expected_len = if *checks_mutation { *len } else { None };
let item = get_heap_item(vm, *heap_id, i, expected_len)?;
let Some(item) = item else {
return Ok(None);
};
self.index += 1;
Ok(Some(item))
}
}
}
pub fn size_hint(&self, heap: &Heap<impl ResourceTracker>) -> usize {
let len = match &self.iter_value {
IterValue::Range { len, .. } | IterValue::IterStr { len, .. } | IterValue::InternBytes { len, .. } => *len,
IterValue::HeapRef { heap_id, len, .. } => {
len.unwrap_or_else(|| {
let HeapData::List(list) = heap.get(*heap_id) else {
panic!("HeapRef with len=None should only be List")
};
list.len()
})
}
};
len.saturating_sub(self.index)
}
pub fn preallocation_hint(
&self,
elem_size: usize,
vm: &VM<'_, impl ResourceTracker>,
) -> Result<usize, ResourceError> {
const MAX_PREALLOCATION_HINT: usize = 65_536;
let hint = self.size_hint(vm.heap);
check_estimated_size(hint.saturating_mul(elem_size), vm.heap.tracker())?;
Ok(hint.min(MAX_PREALLOCATION_HINT))
}
pub fn collect<T: FromIterator<Value>>(self, vm: &mut VM<'_, impl ResourceTracker>) -> RunResult<T> {
let mut guard = HeapGuard::new(self, vm);
let (this, vm) = guard.as_parts_mut();
HeapedMontyIter {
iter: this,
vm,
yielded: 0,
}
.collect()
}
}
struct HeapedMontyIter<'this, 'h, T: ResourceTracker> {
iter: &'this mut MontyIter,
vm: &'this mut VM<'h, T>,
yielded: usize,
}
impl<T: ResourceTracker> Iterator for HeapedMontyIter<'_, '_, T> {
type Item = RunResult<Value>;
fn next(&mut self) -> Option<Self::Item> {
match self.iter.for_next(self.vm) {
Ok(None) => None,
Err(e) => Some(Err(e)),
Ok(Some(value)) => {
self.yielded += 1;
let estimated = self.yielded.saturating_mul(VALUE_SIZE);
match check_estimated_size(estimated, self.vm.heap.tracker()) {
Ok(()) => Some(Ok(value)),
Err(e) => Some(Err(e.into())),
}
}
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = self.iter.size_hint(self.vm.heap);
(remaining, Some(remaining))
}
}
impl<'h> HeapRead<'h, MontyIter> {
pub(crate) fn advance(&mut self, vm: &mut VM<'h, impl ResourceTracker>) -> RunResult<Option<Value>> {
let this = self.get_mut(vm.heap);
match &mut this.iter_value {
IterValue::Range { next, step, len } => {
if this.index >= *len {
Ok(None)
} else {
let value = *next;
*next += *step;
this.index += 1;
Ok(Some(Value::Int(value)))
}
}
IterValue::IterStr {
string,
byte_offset,
len,
} => {
if this.index >= *len {
Ok(None)
} else {
let c = string[*byte_offset..]
.chars()
.next()
.expect("index < len implies char exists");
this.index += 1;
*byte_offset += c.len_utf8();
Ok(Some(allocate_char(c, vm.heap)?))
}
}
IterValue::InternBytes { bytes_id, len } => {
if this.index >= *len {
Ok(None)
} else {
let i = this.index;
this.index += 1;
let bytes = vm.interns.get_bytes(*bytes_id);
Ok(Some(Value::Int(i64::from(bytes[i]))))
}
}
IterValue::HeapRef {
heap_id,
len,
checks_mutation,
} => {
if let Some(l) = len
&& this.index >= *l
{
return Ok(None);
}
let heap_id = *heap_id;
let expected_len = if *checks_mutation { *len } else { None };
let index = this.index;
let item = get_heap_item(vm, heap_id, index, expected_len)?;
let Some(item) = item else {
return Ok(None);
};
self.get_mut(vm.heap).index += 1;
Ok(Some(item))
}
}
}
}
fn get_heap_item(
vm: &VM<'_, impl ResourceTracker>,
heap_id: HeapId,
index: usize,
expected_len: Option<usize>,
) -> RunResult<Option<Value>> {
match vm.heap.get(heap_id) {
HeapData::List(list) => {
if index >= list.len() {
return Ok(None);
}
Ok(Some(list.as_slice()[index].clone_with_heap(vm)))
}
HeapData::Tuple(tuple) => Ok(Some(tuple.as_slice()[index].clone_with_heap(vm))),
HeapData::NamedTuple(namedtuple) => Ok(Some(namedtuple.as_vec()[index].clone_with_heap(vm))),
HeapData::Dict(dict) => {
if let Some(expected) = expected_len
&& dict.len() != expected
{
return Err(ExcType::runtime_error_dict_changed_size());
}
Ok(Some(
dict.key_at(index).expect("index should be valid").clone_with_heap(vm),
))
}
HeapData::DictKeysView(view) => {
let dict = view.dict(vm.heap);
if let Some(expected) = expected_len
&& dict.len() != expected
{
return Err(ExcType::runtime_error_dict_changed_size());
}
Ok(Some(
dict.key_at(index).expect("index should be valid").clone_with_heap(vm),
))
}
HeapData::DictItemsView(view) => {
let dict = view.dict(vm.heap);
if let Some(expected) = expected_len
&& dict.len() != expected
{
return Err(ExcType::runtime_error_dict_changed_size());
}
let (key, value) = dict.item_at(index).expect("index should be valid");
Ok(Some(super::allocate_tuple(
smallvec::smallvec![key.clone_with_heap(vm), value.clone_with_heap(vm)],
vm.heap,
)?))
}
HeapData::DictValuesView(view) => {
let dict = view.dict(vm.heap);
if let Some(expected) = expected_len
&& dict.len() != expected
{
return Err(ExcType::runtime_error_dict_changed_size());
}
Ok(Some(
dict.value_at(index).expect("index should be valid").clone_with_heap(vm),
))
}
HeapData::Bytes(bytes) => Ok(Some(Value::Int(i64::from(bytes.as_slice()[index])))),
HeapData::Set(set) => {
if let Some(expected) = expected_len
&& set.len() != expected
{
return Err(ExcType::runtime_error_set_changed_size());
}
Ok(Some(
set.storage()
.value_at(index)
.expect("index should be valid")
.clone_with_heap(vm),
))
}
HeapData::FrozenSet(frozenset) => Ok(Some(
frozenset
.storage()
.value_at(index)
.expect("index should be valid")
.clone_with_heap(vm),
)),
_ => panic!("get_heap_item: unexpected heap data type"),
}
}
pub fn iterator_next(
iter_value: &Value,
default: Option<Value>,
vm: &mut VM<'_, impl ResourceTracker>,
) -> RunResult<Value> {
let mut default_guard = HeapGuard::new(default, vm);
let vm = default_guard.heap();
let Value::Ref(iter_id) = iter_value else {
return Err(ExcType::type_error_not_iterable(&iter_value.py_type_name(vm)));
};
let result = match vm.heap.read(*iter_id) {
HeapReadOutput::Iter(mut iter) => iter.advance(vm)?,
other => {
let data_type = other.py_type(vm).name(vm.heap, vm.interns);
return Err(ExcType::type_error(format!("'{data_type}' object is not an iterator")));
}
};
match result {
Some(item) => Ok(item),
None => {
match default_guard.into_inner() {
Some(d) => Ok(d),
None => Err(ExcType::stop_iteration()),
}
}
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
enum IterValue {
Range {
next: i64,
step: i64,
len: usize,
},
IterStr {
string: String,
byte_offset: usize,
len: usize,
},
InternBytes { bytes_id: BytesId, len: usize },
HeapRef {
heap_id: HeapId,
len: Option<usize>,
checks_mutation: bool,
},
}
impl IterValue {
fn new(value: &Value, vm: &mut VM<'_, impl ResourceTracker>) -> Option<Self> {
match &value {
Value::InternString(string_id) => Some(Self::from_str(vm.interns.get_str(*string_id))),
Value::InternBytes(bytes_id) => Some(Self::from_intern_bytes(*bytes_id, vm.interns)),
Value::Ref(heap_id) => Self::from_heap_data(*heap_id, vm.heap),
_ => None,
}
}
fn from_range(range: &Range) -> Self {
Self::Range {
next: range.start,
step: range.step,
len: range.len(),
}
}
fn from_str(s: &str) -> Self {
let len = s.chars().count();
Self::IterStr {
string: s.to_owned(),
byte_offset: 0,
len,
}
}
fn from_intern_bytes(bytes_id: BytesId, interns: &Interns) -> Self {
let bytes = interns.get_bytes(bytes_id);
Self::InternBytes {
bytes_id,
len: bytes.len(),
}
}
fn from_heap_data(heap_id: HeapId, heap: &Heap<impl ResourceTracker>) -> Option<Self> {
match heap.get(heap_id) {
HeapData::List(_) => Some(Self::HeapRef {
heap_id,
len: None,
checks_mutation: false,
}),
HeapData::Tuple(tuple) => Some(Self::HeapRef {
heap_id,
len: Some(tuple.as_slice().len()),
checks_mutation: false,
}),
HeapData::NamedTuple(namedtuple) => Some(Self::HeapRef {
heap_id,
len: Some(namedtuple.len()),
checks_mutation: false,
}),
HeapData::Bytes(b) => Some(Self::HeapRef {
heap_id,
len: Some(b.len()),
checks_mutation: false,
}),
HeapData::FrozenSet(frozenset) => Some(Self::HeapRef {
heap_id,
len: Some(frozenset.len()),
checks_mutation: false,
}),
HeapData::Dict(dict) => Some(Self::HeapRef {
heap_id,
len: Some(dict.len()),
checks_mutation: true,
}),
HeapData::DictKeysView(view) => Some(Self::HeapRef {
heap_id,
len: Some(view.dict(heap).len()),
checks_mutation: true,
}),
HeapData::DictItemsView(view) => Some(Self::HeapRef {
heap_id,
len: Some(view.dict(heap).len()),
checks_mutation: true,
}),
HeapData::DictValuesView(view) => Some(Self::HeapRef {
heap_id,
len: Some(view.dict(heap).len()),
checks_mutation: true,
}),
HeapData::Set(set) => Some(Self::HeapRef {
heap_id,
len: Some(set.len()),
checks_mutation: true,
}),
HeapData::Str(s) => Some(Self::from_str(s.as_str())),
HeapData::Range(range) => Some(Self::from_range(range)),
_ => None,
}
}
}
impl DropWithHeap for MontyIter {
#[inline]
fn drop_with_heap<H: ContainsHeap>(self, heap: &mut H) {
Self::drop_with_heap(self, heap);
}
}
impl HeapItem for MontyIter {
fn py_estimate_size(&self) -> usize {
mem::size_of::<Self>()
}
fn py_dec_ref_ids(&mut self, stack: &mut Vec<HeapId>) {
self.value.py_dec_ref_ids(stack);
}
}