use alloc::sync::Arc;
use alloc::vec::Vec;
use brink_format::Value;
use crate::collection_ops::iteration_sequence;
use crate::error::RuntimeError;
#[derive(Debug, Clone)]
pub struct ValueIter {
seq: IterSeq,
idx: u64,
}
#[derive(Debug, Clone)]
enum IterSeq {
Seq(Arc<Vec<Value>>),
Range { start: i64, len: u64 },
}
impl ValueIter {
pub fn new(iterable: &Value) -> Result<Self, RuntimeError> {
let seq = match (iterable.as_range(), iterable.range_len()) {
(Some((start, _, _)), Some(len)) => IterSeq::Range {
start: i64::from(start),
#[expect(clippy::cast_sign_loss, reason = "range_len is never negative")]
len: len as u64,
},
_ => IterSeq::Seq(iteration_sequence(iterable.clone())?),
};
Ok(Self { seq, idx: 0 })
}
#[must_use]
pub fn remaining(&self) -> usize {
let total = match &self.seq {
IterSeq::Seq(seq) => seq.len() as u64,
IterSeq::Range { len, .. } => *len,
};
usize::try_from(total.saturating_sub(self.idx)).unwrap_or(usize::MAX)
}
}
impl Iterator for ValueIter {
type Item = Value;
fn next(&mut self) -> Option<Value> {
let item = match &self.seq {
IterSeq::Seq(seq) => seq.get(usize::try_from(self.idx).ok()?).cloned()?,
IterSeq::Range { start, len } => {
if self.idx >= *len {
return None;
}
#[expect(
clippy::cast_possible_wrap,
clippy::cast_possible_truncation,
reason = "start + idx is an element of the range by construction, so it fits i32"
)]
Value::Int((start + self.idx as i64) as i32)
}
};
self.idx += 1;
Some(item)
}
fn size_hint(&self) -> (usize, Option<usize>) {
let n = self.remaining();
(n, Some(n))
}
}
#[cfg(test)]
mod tests {
use brink_format::OrderedMap;
use super::*;
#[test]
fn range_iterates_elements_in_order_without_materializing() {
let r = Value::range(2, 5, false);
let it = ValueIter::new(&r).unwrap();
assert_eq!(it.remaining(), 3);
let got: Vec<Value> = it.collect();
assert_eq!(got, vec![Value::Int(2), Value::Int(3), Value::Int(4)]);
let got: Vec<Value> = ValueIter::new(&Value::range(-1, 1, true))
.unwrap()
.collect();
assert_eq!(got, vec![Value::Int(-1), Value::Int(0), Value::Int(1)]);
for r in [Value::range(0, 0, false), Value::range(5, 2, true)] {
let mut it = ValueIter::new(&r).unwrap();
assert_eq!(it.remaining(), 0);
assert_eq!(it.next(), None);
assert_eq!(it.next(), None);
}
}
#[test]
fn array_iterates_values_in_order() {
let a = Value::array(vec![Value::Int(3), Value::Int(1), Value::Int(2)]);
let mut it = ValueIter::new(&a).unwrap();
assert_eq!(it.remaining(), 3);
assert_eq!(it.next(), Some(Value::Int(3)));
assert_eq!(it.next(), Some(Value::Int(1)));
assert_eq!(it.next(), Some(Value::Int(2)));
assert_eq!(it.next(), None);
}
#[test]
fn map_iterates_keys_in_insertion_order() {
let mut m = OrderedMap::new();
m.insert(brink_format::MapKey::Str("z".into()), Value::Int(1));
m.insert(brink_format::MapKey::Str("a".into()), Value::Int(2));
m.insert(brink_format::MapKey::Int(7), Value::Int(3));
let mut it = ValueIter::new(&Value::map(m)).unwrap();
assert_eq!(it.next(), Some(Value::String("z".into())));
assert_eq!(it.next(), Some(Value::String("a".into())));
assert_eq!(it.next(), Some(Value::Int(7)));
assert_eq!(it.next(), None);
}
#[test]
fn none_is_terminal_and_sticky() {
let a = Value::array(vec![Value::Int(1)]);
let mut it = ValueIter::new(&a).unwrap();
assert_eq!(it.next(), Some(Value::Int(1)));
for _ in 0..16 {
assert_eq!(it.next(), None);
}
assert_eq!(it.remaining(), 0);
}
#[test]
fn non_iterable_faults_not_indexable() {
let err = ValueIter::new(&Value::Int(3)).unwrap_err();
assert!(matches!(err, RuntimeError::NotIndexable("int")), "{err:?}");
}
#[test]
fn snapshot_is_immune_to_source_mutation() {
let mut a = Value::array(vec![Value::Int(1), Value::Int(2)]);
let mut it = ValueIter::new(&a).unwrap();
if let Some(items) = a.array_make_mut() {
items.push(Value::Int(3));
}
assert_eq!(it.next(), Some(Value::Int(1)));
assert_eq!(it.next(), Some(Value::Int(2)));
assert_eq!(it.next(), None);
}
}