use crate::runtime::{Properties, Property, Substance};
use crate::types::{BaseUnit, Dimensionality, Number, Numeric};
use std::collections::BTreeMap;
use std::iter::Peekable;
use std::str::{Chars, FromStr};
use std::sync::Arc;
enum Token {
Symbol(String),
Count(u32),
Error,
}
#[derive(Clone)]
struct TokenIterator<'a>(Peekable<Chars<'a>>);
impl<'a> TokenIterator<'a> {
pub fn new(input: &'a str) -> TokenIterator<'a> {
TokenIterator(input.chars().peekable())
}
}
impl<'a> Iterator for TokenIterator<'a> {
type Item = Token;
fn next(&mut self) -> Option<Token> {
self.0.peek()?;
let res = match self.0.next().unwrap() {
letter @ 'A'..='Z' => {
let mut symbol = String::new();
symbol.push(letter);
if let Some('a'..='z') = self.0.peek().cloned() {
symbol.push(self.0.next().unwrap())
}
Token::Symbol(symbol)
}
digit @ '0'..='9' => {
let mut integer = String::new();
integer.push(digit);
while let Some('0'..='9') = self.0.peek().cloned() {
integer.push(self.0.next().unwrap())
}
Token::Count(u32::from_str(&integer).unwrap())
}
_ => Token::Error,
};
Some(res)
}
}
pub fn substance_from_formula(
formula: &str,
symbols: &BTreeMap<String, String>,
substances: &BTreeMap<String, Substance>,
) -> Option<Substance> {
let mut molar_mass_unit = Dimensionality::new();
molar_mass_unit.insert(BaseUnit::new("kg"), 1);
molar_mass_unit.insert(BaseUnit::new("mol"), -1);
let mut total_molar_mass = Number {
value: Numeric::from(0),
unit: molar_mass_unit,
};
let mut iter = TokenIterator::new(formula).peekable();
while let Some(token) = iter.next() {
match token {
Token::Symbol(ref sym) if symbols.contains_key(sym) => {
let count = match iter.peek() {
Some(&Token::Count(n)) => {
iter.next().unwrap();
Number::new(Numeric::from(n as i64))
}
_ => Number::one(),
};
let subst = substances.get(symbols.get(sym).unwrap()).unwrap();
match subst.get("molar_mass") {
Ok(subst_molar_mass) => {
let subst_molar_mass = (&subst_molar_mass * &count).unwrap();
total_molar_mass = (&total_molar_mass + &subst_molar_mass).unwrap();
}
Err(_) => return None,
}
}
_ => return None,
}
}
let mut props = BTreeMap::new();
props.insert(
"molar_mass".to_owned(),
Property {
output: total_molar_mass,
output_name: "mass".to_owned(),
input: Number::one(),
input_name: "amount".to_owned(),
doc: None,
},
);
Some(Substance {
amount: Number::one(),
properties: Arc::new(Properties {
name: formula.to_owned(),
properties: props,
}),
})
}