1use crate::atom::{Atom, AtomParseError};
2use rust_decimal::Decimal;
3use std::{borrow::Cow, error::Error, fmt, str::FromStr};
4
5#[derive(Debug, Clone)]
7pub enum MoleculeParseError<'a> {
8 NoAtomNumber,
9 InvalidAtomNumber(Cow<'a, str>, std::num::ParseIntError),
10 NoComment,
11 InvalidAtom(Cow<'a, str>, AtomParseError<'a>),
12 InvalidNumberOfAtoms(usize, usize),
13}
14
15impl<'a> MoleculeParseError<'a> {
16 pub fn into_owned(self) -> MoleculeParseError<'static> {
17 match self {
18 Self::NoAtomNumber => MoleculeParseError::NoAtomNumber,
19 Self::InvalidAtomNumber(input, err) => {
20 MoleculeParseError::InvalidAtomNumber(Cow::Owned(input.into_owned()), err)
21 }
22 Self::NoComment => MoleculeParseError::NoComment,
23 Self::InvalidAtom(input, err) => {
24 MoleculeParseError::InvalidAtom(Cow::Owned(input.into_owned()), err.into_owned())
25 }
26 Self::InvalidNumberOfAtoms(found, expected) => {
27 MoleculeParseError::InvalidNumberOfAtoms(found, expected)
28 }
29 }
30 }
31}
32
33impl<'a> fmt::Display for MoleculeParseError<'a> {
34 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35 match self {
36 Self::NoAtomNumber => write!(f, "No atom number found"),
37 Self::InvalidAtomNumber(input, err) => {
38 write!(f, "Invalid atom number '{input}': {err}")
39 }
40 Self::NoComment => write!(f, "No comment found"),
41 Self::InvalidAtom(input, err) => write!(f, "Invalid atom '{input}': {err}"),
42 Self::InvalidNumberOfAtoms(found, expected) => {
43 write!(
44 f,
45 "Invalid number of coordinates. Found {found}, expected {expected}"
46 )
47 }
48 }
49 }
50}
51
52impl Error for MoleculeParseError<'static> {
53 fn source(&self) -> Option<&(dyn Error + 'static)> {
54 match self {
55 Self::InvalidAtomNumber(_, err) => Some(err),
56 Self::InvalidAtom(_, err) => Some(err),
57 _ => None,
58 }
59 }
60}
61
62#[derive(Debug, Clone, Default, PartialEq, Eq)]
64pub struct Molecule<'a> {
65 pub comment: Cow<'a, str>,
66 pub atoms: Vec<Atom<'a>>,
67}
68
69impl<'a> Molecule<'a> {
70 pub fn parse(string: &'a str) -> Result<Self, MoleculeParseError> {
71 let mut lines = string.lines();
72 let molecule = Self::parse_lines(&mut lines)?;
73 let remaining = lines.count();
74 if remaining > 0 {
75 return Err(MoleculeParseError::InvalidNumberOfAtoms(
76 molecule.atoms.len() + remaining,
77 molecule.atoms.len(),
78 ));
79 }
80 Ok(molecule)
81 }
82
83 pub(crate) fn parse_lines(
84 lines: &mut impl Iterator<Item = &'a str>,
85 ) -> Result<Self, MoleculeParseError<'a>> {
86 let atom_number: usize = if let Some(atom_number) = lines.next() {
87 atom_number
88 .parse()
89 .map_err(|e| MoleculeParseError::InvalidAtomNumber(Cow::Borrowed(atom_number), e))?
90 } else {
91 return Err(MoleculeParseError::NoAtomNumber);
92 };
93 let comment = lines
94 .next()
95 .map(Cow::Borrowed)
96 .ok_or(MoleculeParseError::NoComment)?;
97 let mut atoms = Vec::with_capacity(atom_number);
98 for line in lines.take(atom_number) {
99 atoms.push(
100 Atom::parse(line)
101 .map_err(|err| MoleculeParseError::InvalidAtom(Cow::Borrowed(line), err))?,
102 );
103 }
104 if atoms.len() < atom_number {
105 return Err(MoleculeParseError::InvalidNumberOfAtoms(
106 atoms.len(),
107 atom_number,
108 ));
109 }
110 Ok(Molecule { comment, atoms })
111 }
112
113 pub fn into_owned(self) -> Molecule<'static> {
114 Molecule {
115 comment: Cow::Owned(self.comment.into_owned()),
116 atoms: self
117 .atoms
118 .into_iter()
119 .map(|atom| atom.into_owned())
120 .collect(),
121 }
122 }
123
124 pub fn symbols(&self) -> impl ExactSizeIterator<Item = &str> {
125 self.atoms.iter().map(|atom| atom.symbol.as_ref())
126 }
127
128 pub fn coordinates(&self) -> impl ExactSizeIterator<Item = [Decimal; 3]> + '_ {
129 self.atoms.iter().map(|atom| atom.coordinates())
130 }
131}
132
133impl<'a> fmt::Display for Molecule<'a> {
134 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135 writeln!(f, "{}", self.atoms.len())?;
136 write!(f, "{}", self.comment)?;
137 for atom in &self.atoms {
138 write!(f, "\n{atom}")?;
139 }
140 Ok(())
141 }
142}
143
144impl FromStr for Molecule<'static> {
145 type Err = MoleculeParseError<'static>;
146 fn from_str(s: &str) -> Result<Self, Self::Err> {
147 Molecule::parse(s)
148 .map(|res| res.into_owned())
149 .map_err(|err| err.into_owned())
150 }
151}