Skip to main content

chem_name_resolver/
lib.rs

1pub mod dict;
2pub mod normalizer;
3pub mod parser;
4mod error;
5
6#[cfg(feature = "wasm")]
7mod wasm;
8
9#[cfg(feature = "python")]
10mod py;
11
12pub use error::ResolveError;
13pub use parser::MolGraph;
14
15use std::borrow::Cow;
16
17#[derive(Debug, Clone, PartialEq, serde::Serialize)]
18pub enum ResolveSource {
19    Dictionary,
20    Parser,
21}
22
23#[derive(Debug, Clone, serde::Serialize)]
24pub struct ResolveResult {
25    pub smiles: String,
26    pub canonical_name: String,
27    pub source: ResolveSource,
28    /// Molecular formula in Hill notation (e.g. "C2H6O").
29    /// None when resolved via DirectSmiles (no MolGraph available).
30    pub molecular_formula: Option<String>,
31    /// Molecular weight in g/mol.
32    /// None when resolved via DirectSmiles (no MolGraph available).
33    pub molecular_weight: Option<f64>,
34}
35
36/// Resolve any chemical name to SMILES.
37///
38/// Pipeline: normalize → dictionary → IUPAC parser
39pub fn resolve(name: &str) -> Result<ResolveResult, ResolveError> {
40    let normalized = normalizer::normalize_lowercase(name);
41
42    if let Some(entry) = dict::lookup_synonym(&normalized) {
43        match entry {
44            dict::DictEntry::CanonicalName(canonical) => {
45                let graph = parser::parse_iupac(canonical)?;
46                let smiles = parser::smiles::to_smiles(&graph);
47                let molecular_formula = Some(parser::formula::molecular_formula(&graph));
48                let molecular_weight = Some(parser::formula::molecular_weight(&graph));
49                return Ok(ResolveResult {
50                    smiles,
51                    canonical_name: canonical.to_string(),
52                    source: ResolveSource::Dictionary,
53                    molecular_formula,
54                    molecular_weight,
55                });
56            }
57            dict::DictEntry::DirectSmiles(smiles) => {
58                return Ok(ResolveResult {
59                    smiles: smiles.to_string(),
60                    canonical_name: normalized.clone(),
61                    source: ResolveSource::Dictionary,
62                    molecular_formula: None,
63                    molecular_weight: None,
64                });
65            }
66        }
67    }
68
69    let graph = parser::parse_iupac(&normalized)?;
70    let smiles = parser::smiles::to_smiles(&graph);
71    let molecular_formula = Some(parser::formula::molecular_formula(&graph));
72    let molecular_weight = Some(parser::formula::molecular_weight(&graph));
73    Ok(ResolveResult {
74        smiles,
75        canonical_name: normalized,
76        source: ResolveSource::Parser,
77        molecular_formula,
78        molecular_weight,
79    })
80}
81
82/// Resolve a batch of chemical names, returning one result per input.
83pub fn resolve_batch(names: &[&str]) -> Vec<Result<ResolveResult, ResolveError>> {
84    names.iter().map(|&n| resolve(n)).collect()
85}
86
87/// Normalize a chemical name (CJK-safe, zero-copy when already normalized).
88pub fn normalize(name: &str) -> Cow<'_, str> {
89    normalizer::normalize(name)
90}
91
92/// Dictionary lookup only (no parsing).
93pub fn lookup(name: &str) -> Option<dict::DictEntry<'static>> {
94    dict::lookup_synonym(name)
95}