use std::io::BufRead;
use thiserror::Error;
use crate::Line;
use crate::Reader;
use crate::alignment::Section;
use crate::alignment::section::Builder;
use crate::alignment::section::builder;
use crate::alignment::section::data;
use crate::alignment::section::data::record::Kind;
use crate::alignment::section::header;
use crate::reader;
#[derive(Debug, Error)]
pub enum ParseError {
#[error("the file abruptly ended in the middle of an alignment section")]
AbruptEndInSection,
#[error("found blank line in alignment section: line {0}")]
BlankLineInSection(usize),
#[error("found alignment data between sections: record: {0}")]
DataBetweenSections(data::Record),
#[error("found header in alignment section: record: {0}")]
HeaderInSection(header::Record),
#[error("reader error: {0}")]
Reader(reader::Error),
}
#[derive(Debug, Error)]
pub enum Error {
#[error("builder error: {0}")]
Builder(builder::Error),
#[error("parse error: {0}")]
Parse(ParseError),
}
type Result<T> = std::result::Result<T, Error>;
#[derive(Debug)]
enum State {
InBetweenSections,
ReadingSection,
}
#[derive(Debug)]
pub struct Sections<'a, T>
where
T: BufRead,
{
reader: &'a mut Reader<T>,
state: State,
line_no: usize,
}
impl<'a, T> Sections<'a, T>
where
T: BufRead,
{
pub(crate) fn new(reader: &'a mut Reader<T>) -> Self {
Self {
reader,
state: State::InBetweenSections,
line_no: 0usize,
}
}
}
impl<T> Iterator for Sections<'_, T>
where
T: BufRead,
{
type Item = Result<Section>;
fn next(&mut self) -> Option<Self::Item> {
let mut builder: Option<Builder> = None;
let mut buffer = String::new();
loop {
let line = match self.reader.read_line(&mut buffer) {
Ok(l) => l,
Err(err) => return Some(Err(Error::Parse(ParseError::Reader(err)))),
};
self.line_no += 1;
let line = match line {
Some(l) => l,
None => match self.state {
State::InBetweenSections => return None,
State::ReadingSection => {
return Some(Err(Error::Parse(ParseError::AbruptEndInSection)));
}
},
};
self.state = match get_state(&self.state, &line, self.line_no) {
Ok(s) => s,
Err(err) => return Some(Err(err)),
};
builder = match builder {
Some(inner_builder) => {
match line {
Line::AlignmentData(record) => Some(inner_builder.push_data(record)),
Line::Empty => Some(inner_builder),
Line::Header(_) => unreachable!(),
}
}
None => {
match line {
Line::AlignmentData(_) => unreachable!(),
Line::Empty => None,
Line::Header(record) => {
Some(Builder::default().header(record).unwrap())
}
}
}
};
match self.state {
State::InBetweenSections => {
if let Some(builder) = builder {
match builder.try_build() {
Ok(section) => return Some(Ok(section)),
Err(err) => return Some(Err(Error::Builder(err))),
}
}
}
State::ReadingSection => {}
}
}
}
}
#[allow(clippy::result_large_err)]
fn get_state(last: &State, line: &Line, line_no: usize) -> Result<State> {
match (last, line) {
(State::InBetweenSections, Line::Empty) => {
Ok(State::InBetweenSections)
}
(State::InBetweenSections, Line::Header(_)) => {
Ok(State::ReadingSection)
}
(State::InBetweenSections, Line::AlignmentData(record)) => {
Err(Error::Parse(ParseError::DataBetweenSections(
record.clone(),
)))
}
(State::ReadingSection, Line::Empty) => {
Err(Error::Parse(ParseError::BlankLineInSection(line_no)))
}
(State::ReadingSection, Line::Header(record)) => {
Err(Error::Parse(ParseError::HeaderInSection(record.clone())))
}
(State::ReadingSection, Line::AlignmentData(record)) => {
match record.kind() {
Kind::NonTerminating => Ok(State::ReadingSection),
Kind::Terminating => Ok(State::InBetweenSections),
}
}
}
}