use qubit_budget::ResourceBudget;
use super::ConversionResource;
use super::error::ScalarItemError;
use super::options::CollectionConversionPolicy;
use super::options::EmptyItemPolicy;
use super::scalar_item::ScalarItem;
#[must_use]
#[derive(Debug)]
pub struct ScalarItems<'a> {
value: &'a str,
delimiters: &'a [char],
ascii_delimiters: Option<[bool; 128]>,
non_ascii_delimiters: Option<Vec<char>>,
split_scalar_strings: bool,
trim_items: bool,
empty_item_policy: EmptyItemPolicy,
max_items: u64,
item_budget: ResourceBudget<ConversionResource, u64>,
next_start: Option<usize>,
next_source_index: usize,
}
impl Clone for ScalarItems<'_> {
fn clone(&self) -> Self {
let mut item_budget = ResourceBudget::new(*self.item_budget.resource(), self.item_budget.limit());
item_budget
.try_consume(self.item_budget.used())
.expect("accepted retained items must fit the cloned limit");
Self {
value: self.value,
delimiters: self.delimiters,
ascii_delimiters: self.ascii_delimiters,
non_ascii_delimiters: self.non_ascii_delimiters.clone(),
split_scalar_strings: self.split_scalar_strings,
trim_items: self.trim_items,
empty_item_policy: self.empty_item_policy,
max_items: self.max_items,
item_budget,
next_start: self.next_start,
next_source_index: self.next_source_index,
}
}
}
impl<'a> ScalarItems<'a> {
pub(super) fn new(policy: &'a CollectionConversionPolicy, max_items: u64, value: &'a str) -> Self {
let delimiters = policy.delimiters();
let split_scalar_strings = policy.split_scalar_strings();
let (ascii_delimiters, non_ascii_delimiters) = if split_scalar_strings && delimiters.len() > 8 {
let mut ascii = [false; 128];
let mut non_ascii = Vec::new();
for &delimiter in delimiters {
if delimiter.is_ascii() {
ascii[delimiter as usize] = true;
} else {
non_ascii.push(delimiter);
}
}
non_ascii.sort_unstable();
non_ascii.dedup();
(Some(ascii), Some(non_ascii))
} else {
(None, None)
};
Self {
value,
delimiters,
ascii_delimiters,
non_ascii_delimiters,
split_scalar_strings,
trim_items: policy.trim_items(),
empty_item_policy: policy.empty_item_policy(),
max_items,
item_budget: ResourceBudget::new(ConversionResource::CollectionItems, max_items),
next_start: Some(0),
next_source_index: 0,
}
}
fn next_raw(&mut self) -> Option<ScalarItem<'a>> {
let start = self.next_start?;
let source_index = self.next_source_index;
self.next_source_index += 1;
if !self.split_scalar_strings {
self.next_start = None;
return Some(ScalarItem {
source_index,
value: self.value,
});
}
let remaining = &self.value[start..];
let ascii_delimiters = self.ascii_delimiters.as_ref();
let non_ascii_delimiters = self.non_ascii_delimiters.as_deref();
let delimiters = self.delimiters;
match remaining.char_indices().find(|(_, character)| {
let Some(ascii) = ascii_delimiters else {
return delimiters.contains(character);
};
if character.is_ascii() {
ascii[*character as usize]
} else {
non_ascii_delimiters.is_some_and(|sorted| sorted.binary_search(character).is_ok())
}
}) {
Some((relative_end, delimiter)) => {
let end = start + relative_end;
self.next_start = Some(end + delimiter.len_utf8());
Some(ScalarItem {
source_index,
value: &self.value[start..end],
})
}
None => {
self.next_start = None;
Some(ScalarItem {
source_index,
value: remaining,
})
}
}
}
#[inline]
fn retain_item(&mut self, item: ScalarItem<'a>) -> Result<ScalarItem<'a>, ScalarItemError> {
if self.item_budget.try_consume(1).is_err() {
self.next_start = None;
return Err(ScalarItemError::item_limit_exceeded(item.source_index, self.max_items));
}
Ok(item)
}
}
impl<'a> Iterator for ScalarItems<'a> {
type Item = Result<ScalarItem<'a>, ScalarItemError>;
fn next(&mut self) -> Option<Self::Item> {
loop {
let mut item = self.next_raw()?;
if self.trim_items {
item.value = item.value.trim();
}
if !item.value.is_empty() {
return Some(self.retain_item(item));
}
match self.empty_item_policy {
EmptyItemPolicy::Keep => return Some(self.retain_item(item)),
EmptyItemPolicy::Skip => {}
EmptyItemPolicy::Reject => {
return Some(Err(ScalarItemError::blank_rejected(item.source_index)));
}
}
}
}
}