Skip to main content

use_chemical_formula/
lib.rs

1#![forbid(unsafe_code)]
2#![allow(clippy::module_name_repetitions)]
3#![doc = include_str!("../README.md")]
4
5//! Structural chemical formula primitives and lightweight parsing.
6
7mod chemical_formula;
8mod element_count;
9mod element_symbol;
10mod error;
11mod formula_group;
12mod formula_part;
13mod formula_term;
14mod hydrate_part;
15mod parse;
16
17pub use chemical_formula::ChemicalFormula;
18pub use element_count::{ElementCount, FormulaMultiplier};
19pub use element_symbol::{ElementSymbol, is_valid_element_symbol};
20pub use error::{FormulaParseError, FormulaValidationError};
21pub use formula_group::FormulaGroup;
22pub use formula_part::FormulaPart;
23pub use formula_term::FormulaTerm;
24pub use hydrate_part::HydratePart;
25
26#[cfg(test)]
27mod tests {
28    use super::{ChemicalFormula, ElementSymbol, FormulaParseError};
29
30    #[test]
31    fn parses_simple_binary_formulas() {
32        let water = ChemicalFormula::parse("H2O").expect("water formula should parse");
33        let carbon_dioxide = ChemicalFormula::parse("CO2").expect("carbon dioxide should parse");
34        let sodium_chloride = ChemicalFormula::parse("NaCl").expect("sodium chloride should parse");
35
36        assert_eq!(water.to_string(), "H2O");
37        assert_eq!(carbon_dioxide.to_string(), "CO2");
38        assert_eq!(sodium_chloride.to_string(), "NaCl");
39        assert_eq!(water.element_counts().get("H"), Some(&2));
40        assert_eq!(water.element_counts().get("O"), Some(&1));
41    }
42
43    #[test]
44    fn parses_one_and_two_letter_symbols() {
45        let iron_oxide = ChemicalFormula::parse("Fe2O3").expect("iron oxide should parse");
46        let counts = iron_oxide.element_counts();
47
48        assert_eq!(iron_oxide.to_string(), "Fe2O3");
49        assert_eq!(counts.get("Fe"), Some(&2));
50        assert_eq!(counts.get("O"), Some(&3));
51        assert_eq!(
52            ElementSymbol::new("Cl").map(|symbol| symbol.to_string()),
53            Ok(String::from("Cl"))
54        );
55    }
56
57    #[test]
58    fn parses_numeric_counts() {
59        let glucose = ChemicalFormula::parse("C6H12O6").expect("glucose formula should parse");
60        let ammonium_nitrate =
61            ChemicalFormula::parse("NH4NO3").expect("ammonium nitrate should parse");
62        let glucose_counts = glucose.element_counts();
63        let nitrate_counts = ammonium_nitrate.element_counts();
64
65        assert_eq!(glucose_counts.get("C"), Some(&6));
66        assert_eq!(glucose_counts.get("H"), Some(&12));
67        assert_eq!(glucose_counts.get("O"), Some(&6));
68        assert_eq!(nitrate_counts.get("N"), Some(&2));
69        assert_eq!(nitrate_counts.get("H"), Some(&4));
70        assert_eq!(nitrate_counts.get("O"), Some(&3));
71    }
72
73    #[test]
74    fn parses_groups_and_group_multipliers() {
75        let calcium_hydroxide =
76            ChemicalFormula::parse("Ca(OH)2").expect("calcium hydroxide should parse");
77        let aluminum_sulfate =
78            ChemicalFormula::parse("Al2(SO4)3").expect("aluminum sulfate should parse");
79        let hydroxide_counts = calcium_hydroxide.element_counts();
80        let sulfate_counts = aluminum_sulfate.element_counts();
81
82        assert_eq!(calcium_hydroxide.to_string(), "Ca(OH)2");
83        assert_eq!(hydroxide_counts.get("Ca"), Some(&1));
84        assert_eq!(hydroxide_counts.get("O"), Some(&2));
85        assert_eq!(hydroxide_counts.get("H"), Some(&2));
86        assert_eq!(aluminum_sulfate.to_string(), "Al2(SO4)3");
87        assert_eq!(sulfate_counts.get("Al"), Some(&2));
88        assert_eq!(sulfate_counts.get("S"), Some(&3));
89        assert_eq!(sulfate_counts.get("O"), Some(&12));
90    }
91
92    #[test]
93    fn parses_hydrate_formulas() {
94        let hydrate = ChemicalFormula::parse("CuSO4·5H2O").expect("hydrate should parse");
95        let ascii_dot = ChemicalFormula::parse("CuSO4.5H2O").expect("dot hydrate should parse");
96        let counts = hydrate.element_counts();
97
98        assert_eq!(hydrate.to_string(), "CuSO4·5H2O");
99        assert_eq!(ascii_dot.to_string(), "CuSO4·5H2O");
100        assert_eq!(counts.get("Cu"), Some(&1));
101        assert_eq!(counts.get("S"), Some(&1));
102        assert_eq!(counts.get("O"), Some(&9));
103        assert_eq!(counts.get("H"), Some(&10));
104    }
105
106    #[test]
107    fn parses_nested_groups() {
108        let formula = ChemicalFormula::parse("K4(ON(SO3)2)2").expect("nested formula should parse");
109        let counts = formula.element_counts();
110
111        assert_eq!(formula.to_string(), "K4(ON(SO3)2)2");
112        assert_eq!(counts.get("K"), Some(&4));
113        assert_eq!(counts.get("O"), Some(&14));
114        assert_eq!(counts.get("N"), Some(&2));
115        assert_eq!(counts.get("S"), Some(&4));
116    }
117
118    #[test]
119    fn rejects_invalid_formulas() {
120        assert_eq!(
121            ChemicalFormula::parse(""),
122            Err(FormulaParseError::EmptyFormula)
123        );
124        assert!(matches!(
125            ChemicalFormula::parse("h2O"),
126            Err(FormulaParseError::InvalidSymbol(_))
127        ));
128        assert_eq!(
129            ChemicalFormula::parse("Ca(OH2"),
130            Err(FormulaParseError::UnmatchedOpenGroup)
131        );
132        assert_eq!(
133            ChemicalFormula::parse("Ca)OH("),
134            Err(FormulaParseError::UnmatchedCloseGroup)
135        );
136        assert_eq!(
137            ChemicalFormula::parse("Ca()2"),
138            Err(FormulaParseError::EmptyGroup)
139        );
140        assert_eq!(
141            ChemicalFormula::parse("H0O"),
142            Err(FormulaParseError::ZeroCount)
143        );
144        assert_eq!(
145            ChemicalFormula::parse("Ca(OH)0"),
146            Err(FormulaParseError::ZeroMultiplier)
147        );
148        assert_eq!(
149            ChemicalFormula::parse("CuSO4·0H2O"),
150            Err(FormulaParseError::ZeroMultiplier)
151        );
152        assert_eq!(
153            ChemicalFormula::parse("CuSO4·"),
154            Err(FormulaParseError::TrailingSeparator)
155        );
156        assert!(matches!(
157            ChemicalFormula::parse("H 2O"),
158            Err(FormulaParseError::UnexpectedCharacter(' '))
159        ));
160    }
161}