molecular-formulas 0.1.8

A Rust crate for parsing, manipulating, and analyzing molecular formulas.
Documentation
//! Submodule defining the expression syntax trees for molecular formulas
//! as found in certain specialized contexts. This format includes residual
//! notations like `R` used in specific scientific fields.

use alloc::vec::Vec;

use elements_rs::{Element, Isotope};

use crate::{
    ChargeLike, ChargedMolecularFormulaMetadata, ChemicalTree, CountLike, MolecularFormulaMetadata,
    ParsableFormula, Residual,
};

#[derive(Debug, PartialEq, Clone, Eq, PartialOrd, Ord, Hash)]
/// A chemical formula which can contain residual notations.
///
/// # Examples
///
/// ```
/// use std::str::FromStr;
///
/// use molecular_formulas::prelude::*;
///
/// let formula = ResidualFormula::<u32, i32>::from_str("C6H5R").unwrap();
/// assert!(formula.contains_residuals());
/// ```
pub struct ResidualFormula<Count: CountLike = u16, Charge: ChargeLike = i16> {
    mixtures: Vec<(Count, ChemicalTree<Count, Charge, Residual>)>,
}

impl<Count: CountLike, Charge: ChargeLike> ResidualFormula<Count, Charge> {
    /// Checks if the formula contains any residual notations.
    #[must_use]
    pub fn contains_residuals(&self) -> bool {
        for (_fraction, tree) in &self.mixtures {
            if tree.contains_extension() {
                return true;
            }
        }
        false
    }
}

impl<Count: CountLike, Charge: ChargeLike> From<Element> for ResidualFormula<Count, Charge> {
    fn from(element: Element) -> Self {
        Self { mixtures: alloc::vec![(Count::one(), ChemicalTree::Element(element))] }
    }
}

impl<Count: CountLike, Charge: ChargeLike> From<Isotope> for ResidualFormula<Count, Charge> {
    fn from(isotope: Isotope) -> Self {
        Self { mixtures: alloc::vec![(Count::one(), ChemicalTree::Isotope(isotope))] }
    }
}

impl<Count: CountLike, Charge: ChargeLike> MolecularFormulaMetadata
    for ResidualFormula<Count, Charge>
{
    type Count = Count;
}

impl<Count: CountLike, Charge: ChargeLike> ChargedMolecularFormulaMetadata
    for ResidualFormula<Count, Charge>
where
    Charge: TryFrom<Count>,
{
    type Charge = Charge;
}

impl<Count: CountLike, Charge: ChargeLike> ParsableFormula for ResidualFormula<Count, Charge>
where
    Isotope: TryFrom<(elements_rs::Element, Count), Error = elements_rs::errors::Error>,
    Charge: TryFrom<Count>,
{
    type StartOutput = ();
    type Tree = ChemicalTree<Count, Charge, Residual>;

    fn on_start<J>(
        _chars: &mut core::iter::Peekable<J>,
    ) -> Result<Self::StartOutput, crate::errors::ParserError>
    where
        J: Iterator<Item = char>,
    {
        Ok(())
    }

    fn from_parsed(
        _start_output: Self::StartOutput,
        mixtures: Vec<(Count, Self::Tree)>,
    ) -> Result<Self, crate::errors::ParserError> {
        assert!(!mixtures.is_empty(), "At least one mixture is required");
        Ok(Self { mixtures })
    }
}

impl<Count: CountLike, Charge: ChargeLike> core::fmt::Display for ResidualFormula<Count, Charge> {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        for (i, (fraction, tree)) in self.mixtures.iter().enumerate() {
            if i > 0 {
                write!(f, ".")?;
            }
            if *fraction != Count::one() {
                write!(f, "{fraction}")?;
            }
            write!(f, "{tree}")?;
        }
        Ok(())
    }
}