#[cfg(test)]
mod tests;
use std::str::FromStr;
use nom::{
bytes::complete::{tag, take_till1, take_while, take_while1},
character::complete::{anychar, digit0},
combinator::{map_opt, map_res, opt, peek},
error::{Error, ErrorKind},
multi::many1,
sequence::delimited,
IResult,
};
use crate::{Compound, Direction, Element, Equation, State};
pub fn parse_equation(orig_i: &str) -> IResult<&str, Equation> {
let (i, lhs) = take_till1(|c: char| c == '<' || c == '-')(orig_i)?;
let (rhs, tag) = take_while1(|c: char| c == '<' || c == '-' || c == '>')(i)?;
let direction =
Direction::from_str(tag).map_err(|_| nom::Err::Error(Error::new(i, ErrorKind::Verify)))?;
let (_, left_cmp) = parse_side(lhs)?;
let (i, right_cmp) = parse_side(rhs)?;
let mut orig_i = orig_i.to_string();
orig_i.truncate(orig_i.trim_end().len());
Ok((
i,
Equation {
left: left_cmp,
right: right_cmp,
direction,
equation: orig_i,
delta_h: 0.0,
},
))
}
fn parse_side(i: &str) -> IResult<&str, Vec<Compound>> {
let i = i.trim_start();
let (i, compounds) = many1(compound_and_plus)(i)?;
Ok((i, compounds))
}
fn parse_element(orig_i: &str) -> IResult<&str, Element> {
let (i, c) = anychar(orig_i)?;
if c.is_lowercase() || !c.is_alphabetic() {
return Err(nom::Err::Error(Error::new(orig_i, ErrorKind::Char)));
}
let alpha_lower = |i: char| i.is_alphabetic() && i.is_lowercase();
let (i, name) = take_while(alpha_lower)(i)?;
let mut c = c.to_string();
c.push_str(name);
let opt_num = opt(digit0);
let (i, num) = map_opt(opt_num, |s: Option<&str>| s.map(str::parse::<usize>))(i)?;
Ok((
i,
Element {
name: c,
count: num.unwrap_or(1),
},
))
}
fn parse_compound(i: &str) -> IResult<&str, Compound> {
let opt_num = opt(digit0);
let (i, num) = map_opt(opt_num, |s: Option<&str>| s.map(str::parse::<usize>))(i)?;
let (i, extra_elements) = many1(bracketed_elements)(i)?;
let mut elements = vec![];
extra_elements.into_iter().for_each(|v| elements.extend(v));
let (i, state) = match delimited(
tag::<_, _, Error<&str>>("("),
map_res(take_while(char::is_alphabetic), State::from_str),
tag(")"),
)(i)
{
Ok((i, state)) => (i, Some(state)),
Err(e) => {
match e {
nom::Err::Error(ref inner) if inner.code == ErrorKind::MapRes => return Err(e),
_ => {}
}
(i, None)
}
};
Ok((
i,
Compound {
elements,
coefficient: num.unwrap_or(1),
state,
},
))
}
fn bracketed_elements(orig_i: &str) -> IResult<&str, Vec<Element>> {
let (i, b) = peek(anychar)(orig_i)?;
if i.chars().next().unwrap_or('a').is_lowercase() {
return Err(nom::Err::Error(Error::new(orig_i, ErrorKind::Verify)));
}
let (i, deep) = if b == '(' {
let (i, _) = anychar::<_, Error<&str>>(i).unwrap();
(i, true)
} else {
(i, false)
};
let (i, mut elements) = many1(parse_element)(i)?;
let (i, b) = match peek(anychar::<_, Error<&str>>)(i) {
Ok((i, b)) => (i, b),
Err(e) => match &e {
nom::Err::Error(e) if e.code == ErrorKind::Eof => return Ok((i, elements)),
_ => return Err(e),
},
};
let (i, coef) = if b == ')' && deep {
let (i, _) = anychar::<_, Error<&str>>(i).unwrap();
let opt_num = opt(digit0);
map_opt(opt_num, |s: Option<&str>| s.map(str::parse::<usize>))(i)?
} else {
(i, Ok(1))
};
for el in &mut elements {
el.count *= coef.as_ref().unwrap_or(&1);
}
Ok((i, elements))
}
fn compound_and_plus(i: &str) -> IResult<&str, Compound> {
let (i, compound) = parse_compound(i)?;
let (i, _) = take_while(|c: char| c.is_whitespace() || c == '+')(i)?;
Ok((i, compound))
}