use crate::parser::{parse_declared_sections, parse_single_frame};
use crate::{error, types};
use std::iter::Peekable;
use std::path::Path;
pub struct ConFrameIterator<'a> {
lines: Peekable<std::str::Lines<'a>>,
bytes: &'a [u8],
cursor: usize,
}
impl<'a> ConFrameIterator<'a> {
pub fn new(file_contents: &'a str) -> Self {
ConFrameIterator {
lines: file_contents.lines().peekable(),
bytes: file_contents.as_bytes(),
cursor: 0,
}
}
fn advance_lines(&mut self, n: usize) -> Result<(), error::ParseError> {
for _ in 0..n {
let rest = &self.bytes[self.cursor..];
match memchr::memchr(b'\n', rest) {
Some(pos) => self.cursor += pos + 1,
None => {
if rest.is_empty() {
return Err(error::ParseError::IncompleteFrame);
}
self.cursor = self.bytes.len();
return Err(error::ParseError::IncompleteFrame);
}
}
}
Ok(())
}
fn read_line_str(&mut self) -> Option<&'a str> {
let rest = &self.bytes[self.cursor..];
if rest.is_empty() {
return None;
}
let (line_bytes, advance) = match memchr::memchr(b'\n', rest) {
Some(pos) => (&rest[..pos], pos + 1),
None => (rest, rest.len()),
};
let trimmed = if line_bytes.last() == Some(&b'\r') {
&line_bytes[..line_bytes.len() - 1]
} else {
line_bytes
};
let line = unsafe { std::str::from_utf8_unchecked(trimmed) };
self.cursor += advance;
Some(line)
}
pub fn forward_fast(&mut self) -> Option<Result<(), error::ParseError>> {
if self.cursor >= self.bytes.len() {
return None;
}
if let Err(e) = self.advance_lines(6) {
return Some(Err(e));
}
let natm_types: usize = match self.read_line_str() {
Some(line) => match crate::parser::parse_line_of_n::<usize>(line, 1) {
Ok(v) => v[0],
Err(e) => return Some(Err(e)),
},
None => return Some(Err(error::ParseError::IncompleteHeader)),
};
let natms_per_type: Vec<usize> = match self.read_line_str() {
Some(line) => match crate::parser::parse_line_of_n(line, natm_types) {
Ok(v) => v,
Err(e) => return Some(Err(e)),
},
None => return Some(Err(error::ParseError::IncompleteHeader)),
};
if let Err(e) = self.advance_lines(1) {
return Some(Err(e));
}
let total_atoms: usize = natms_per_type.iter().sum();
let coord_block_lines = total_atoms + natm_types * 2;
if let Err(e) = self.advance_lines(coord_block_lines) {
return Some(Err(e));
}
loop {
let rest = &self.bytes[self.cursor..];
let next_eol = memchr::memchr(b'\n', rest);
let line = match next_eol {
Some(pos) => &rest[..pos],
None => rest,
};
let is_blank = line
.iter()
.all(|b| matches!(b, b' ' | b'\t' | b'\r'));
if !is_blank || line.is_empty() && self.cursor >= self.bytes.len() {
break;
}
if !is_blank {
break;
}
self.cursor += next_eol.map(|p| p + 1).unwrap_or(rest.len());
if let Err(e) = self.advance_lines(coord_block_lines) {
return Some(Err(e));
}
}
Some(Ok(()))
}
pub fn forward(&mut self) -> Option<Result<(), error::ParseError>> {
self.lines.peek()?;
for _ in 0..6 {
if self.lines.next().is_none() {
return Some(Err(error::ParseError::IncompleteHeader));
}
}
let natm_types: usize = match self.lines.next() {
Some(line) => match crate::parser::parse_line_of_n::<usize>(line, 1) {
Ok(v) => v[0],
Err(e) => return Some(Err(e)),
},
None => return Some(Err(error::ParseError::IncompleteHeader)),
};
let natms_per_type: Vec<usize> = match self.lines.next() {
Some(line) => match crate::parser::parse_line_of_n(line, natm_types) {
Ok(v) => v,
Err(e) => return Some(Err(e)),
},
None => return Some(Err(error::ParseError::IncompleteHeader)),
};
if self.lines.next().is_none() {
return Some(Err(error::ParseError::IncompleteHeader));
}
let total_atoms: usize = natms_per_type.iter().sum();
let non_atom_lines = natm_types * 2;
let lines_to_skip = total_atoms + non_atom_lines;
for _ in 0..lines_to_skip {
if self.lines.next().is_none() {
return Some(Err(error::ParseError::IncompleteFrame));
}
}
loop {
match self.lines.peek() {
Some(line) if line.trim().is_empty() => {
self.lines.next(); let section_lines = total_atoms + non_atom_lines;
for _ in 0..section_lines {
if self.lines.next().is_none() {
return Some(Err(error::ParseError::IncompleteFrame));
}
}
}
_ => break,
}
}
Some(Ok(()))
}
}
impl<'a> Iterator for ConFrameIterator<'a> {
type Item = Result<types::ConFrame, error::ParseError>;
fn next(&mut self) -> Option<Self::Item> {
self.lines.peek()?;
let mut frame = match parse_single_frame(&mut self.lines) {
Ok(f) => f,
Err(e) => return Some(Err(e)),
};
match parse_declared_sections(&mut self.lines, &mut frame.header, &mut frame.atom_data) {
Ok(_) => {}
Err(e) => return Some(Err(e)),
}
Some(Ok(frame))
}
}
pub fn read_all_frames(path: &Path) -> Result<Vec<types::ConFrame>, Box<dyn std::error::Error>> {
let contents = crate::compression::read_file_contents(path)?;
let text = contents.as_str()?;
let iter = ConFrameIterator::new(text);
let frames: Result<Vec<_>, _> = iter.collect();
Ok(frames?)
}
pub fn read_first_frame(path: &Path) -> Result<types::ConFrame, Box<dyn std::error::Error>> {
let contents = crate::compression::read_file_contents(path)?;
let text = contents.as_str()?;
let mut iter = ConFrameIterator::new(text);
match iter.next() {
Some(Ok(frame)) => Ok(frame),
Some(Err(e)) => Err(Box::new(e)),
None => Err("No frames found in file".into()),
}
}
#[cfg(feature = "parallel")]
pub fn parse_frames_parallel(
file_contents: &str,
) -> Vec<Result<types::ConFrame, error::ParseError>> {
use rayon::prelude::*;
let mut boundaries: Vec<usize> = Vec::new();
let mut scanner = ConFrameIterator::new(file_contents);
loop {
let start = scanner.cursor;
if start >= scanner.bytes.len() {
break;
}
boundaries.push(start);
match scanner.forward_fast() {
Some(Ok(())) => {}
Some(Err(_)) | None => break,
}
}
let num_frames = boundaries.len();
(0..num_frames)
.into_par_iter()
.map(|i| {
let start = boundaries[i];
let end = if i + 1 < num_frames {
boundaries[i + 1]
} else {
file_contents.len()
};
let chunk = &file_contents[start..end];
let mut iter = ConFrameIterator::new(chunk);
match iter.next() {
Some(result) => result,
None => Err(error::ParseError::IncompleteFrame),
}
})
.collect()
}