use crate::parser::lexical::skip_step_trivia;
pub struct StepListItems<'a> {
bytes: &'a [u8],
pos: usize,
first: bool,
state: ListState,
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum ListState {
Open,
Closed,
Unreadable,
}
impl<'a> StepListItems<'a> {
pub fn of_record(record: &'a [u8]) -> Option<Self> {
argument_list_start(record).map(|pos| Self::after_open(record, pos))
}
pub fn of_list(value: &'a [u8]) -> Option<Self> {
let open = skip_step_trivia(value, 0)?;
(value.get(open) == Some(&b'(')).then(|| Self::after_open(value, open + 1))
}
fn after_open(bytes: &'a [u8], pos: usize) -> Self {
Self { bytes, pos, first: true, state: ListState::Open }
}
pub fn closed(&self) -> bool {
self.state == ListState::Closed
}
}
impl<'a> Iterator for StepListItems<'a> {
type Item = &'a [u8];
fn next(&mut self) -> Option<&'a [u8]> {
if self.state != ListState::Open {
return None;
}
let bytes = self.bytes;
let mut pos = self.pos;
let (mut start, mut end) = (None, pos);
let mut depth = 0usize;
loop {
let Some(next) = skip_step_trivia(bytes, pos) else {
break;
};
pos = next;
let Some(&b) = bytes.get(pos) else {
break;
};
let value_start = pos;
match b {
b',' | b')' if depth == 0 => {
let first = std::mem::replace(&mut self.first, false);
self.pos = pos + 1;
if b == b')' {
self.state = ListState::Closed;
if first && start.is_none() {
return None; }
}
return Some(start.map_or(&bytes[pos..pos], |s| &bytes[s..end]));
}
b'\'' => {
pos += 1;
loop {
match bytes.get(pos) {
Some(b'\'') if bytes.get(pos + 1) == Some(&b'\'') => pos += 2,
Some(b'\'') => break,
Some(_) => pos += 1,
None => {
self.state = ListState::Unreadable;
return None;
}
}
}
}
b'(' => depth += 1,
b')' => depth -= 1,
_ => {}
}
pos += 1;
start.get_or_insert(value_start);
end = pos;
}
self.state = ListState::Unreadable;
None
}
}
pub fn nth_attribute_is_present(record: &[u8], index: usize) -> bool {
let Some(mut attributes) = StepListItems::of_record(record) else {
return false;
};
let holds_a_value = attributes
.nth(index)
.is_some_and(|value| !matches!(value.first(), None | Some(b'$')));
holds_a_value && {
attributes.by_ref().for_each(drop);
attributes.closed()
}
}
pub(crate) fn argument_list_start(record: &[u8]) -> Option<usize> {
let mut pos = 0;
loop {
pos = skip_step_trivia(record, pos)?;
match record.get(pos)? {
b'(' => return Some(pos + 1),
_ => pos += 1,
}
}
}
#[cfg(test)]
#[path = "scanner_attributes_tests.rs"]
mod tests;