1use std::fmt;
14
15const SYMBOLS: [&str; 119] = [
17 "", "H", "He", "Li", "Be", "B", "C", "N", "O", "F", "Ne", "Na", "Mg", "Al", "Si", "P", "S",
18 "Cl", "Ar", "K", "Ca", "Sc", "Ti", "V", "Cr", "Mn", "Fe", "Co", "Ni", "Cu", "Zn", "Ga", "Ge",
19 "As", "Se", "Br", "Kr", "Rb", "Sr", "Y", "Zr", "Nb", "Mo", "Tc", "Ru", "Rh", "Pd", "Ag", "Cd",
20 "In", "Sn", "Sb", "Te", "I", "Xe", "Cs", "Ba", "La", "Ce", "Pr", "Nd", "Pm", "Sm", "Eu", "Gd",
21 "Tb", "Dy", "Ho", "Er", "Tm", "Yb", "Lu", "Hf", "Ta", "W", "Re", "Os", "Ir", "Pt", "Au", "Hg",
22 "Tl", "Pb", "Bi", "Po", "At", "Rn", "Fr", "Ra", "Ac", "Th", "Pa", "U", "Np", "Pu", "Am", "Cm",
23 "Bk", "Cf", "Es", "Fm", "Md", "No", "Lr", "Rf", "Db", "Sg", "Bh", "Hs", "Mt", "Ds", "Rg", "Cn",
24 "Nh", "Fl", "Mc", "Lv", "Ts", "Og",
25];
26
27type Key = [u8; 2];
30
31const LETTERS: usize = 26;
32const SECOND_LETTER_OPTIONS: usize = LETTERS + 1;
33const KEY_COUNT: usize = LETTERS * SECOND_LETTER_OPTIONS;
34
35const KEYS: [Key; 119] = build_keys();
40
41const ELEMENT_BY_KEY: [u8; KEY_COUNT] = build_element_lookup();
43
44const fn build_keys() -> [Key; 119] {
45 let mut keys = [[0u8; 2]; 119];
46 let mut z = 1;
47
48 while z < SYMBOLS.len() {
49 let bytes = SYMBOLS[z].as_bytes();
50
51 keys[z] = if bytes.len() == 1 {
52 [bytes[0], 0]
53 } else {
54 [bytes[0], bytes[1]]
55 };
56
57 z += 1;
58 }
59
60 keys
61}
62
63const fn build_element_lookup() -> [u8; KEY_COUNT] {
64 let mut lookup = [0u8; KEY_COUNT];
65 let mut z = 1u8;
66
67 while (z as usize) < KEYS.len() {
68 lookup[key_index(KEYS[z as usize])] = z;
69 z += 1;
70 }
71
72 lookup
73}
74
75const fn key_of(bytes: &[u8]) -> Option<Key> {
78 match bytes {
79 [a] if a.is_ascii_alphabetic() => Some([a.to_ascii_uppercase(), 0]),
80 [a, b] if a.is_ascii_alphabetic() && b.is_ascii_alphabetic() => {
81 Some([a.to_ascii_uppercase(), b.to_ascii_lowercase()])
82 }
83 _ => None,
84 }
85}
86
87#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
103#[repr(transparent)]
104pub struct Element(u8);
105
106impl Element {
107 pub const UNKNOWN: Self = Self(0);
109 pub const HYDROGEN: Self = Self(1);
111 pub const CARBON: Self = Self(6);
113 pub const NITROGEN: Self = Self(7);
115 pub const OXYGEN: Self = Self(8);
117 pub const FLUORINE: Self = Self(9);
119 pub const PHOSPHORUS: Self = Self(15);
121 pub const SULFUR: Self = Self(16);
123 pub const CHLORINE: Self = Self(17);
125 pub const CALCIUM: Self = Self(20);
127 pub const IRON: Self = Self(26);
129 pub const ZINC: Self = Self(30);
131 pub const SELENIUM: Self = Self(34);
133
134 pub const MAX_ATOMIC_NUMBER: u8 = 118;
136
137 #[must_use]
140 pub const fn from_atomic_number(z: u8) -> Self {
141 if z <= Self::MAX_ATOMIC_NUMBER {
142 Self(z)
143 } else {
144 Self::UNKNOWN
145 }
146 }
147
148 #[must_use]
151 pub const fn atomic_number(self) -> u8 {
152 self.0
153 }
154
155 #[must_use]
157 pub fn symbol(self) -> &'static str {
158 match SYMBOLS.get(usize::from(self.0)) {
159 Some(symbol) => symbol,
160 None => "",
161 }
162 }
163
164 #[must_use]
166 pub const fn is_unknown(self) -> bool {
167 self.0 == 0
168 }
169
170 #[must_use]
175 pub const fn is_hydrogen(self) -> bool {
176 self.0 == Self::HYDROGEN.0
177 }
178
179 #[must_use]
184 pub fn from_symbol(text: &str) -> Option<Self> {
185 let text = text.trim();
186
187 if text.eq_ignore_ascii_case("D") || text.eq_ignore_ascii_case("T") {
188 return Some(Self::HYDROGEN);
189 }
190
191 key_of(text.as_bytes()).and_then(element_from_key)
192 }
193
194 #[must_use]
209 pub fn infer_from_pdb_atom_name(field: &str) -> Self {
210 let bytes = field.as_bytes();
211
212 if starts_with_letter(bytes)
213 && let Some(element) = first_two_letter_element(bytes)
214 {
215 return element;
216 }
217
218 Self::first_letter_element(bytes)
219 }
220
221 #[must_use]
227 pub fn infer_from_name(name: &str) -> Self {
228 let bytes = name.as_bytes();
229
230 if let Some(element) = first_two_letter_element(bytes) {
231 return element;
232 }
233
234 Self::first_letter_element(bytes)
235 }
236
237 fn first_letter_element(bytes: &[u8]) -> Self {
238 let Some(letter) = bytes.iter().copied().find(u8::is_ascii_alphabetic) else {
239 return Self::UNKNOWN;
240 };
241
242 match key_of(&[letter]).and_then(element_from_key) {
243 Some(element) => element,
244 None => Self::UNKNOWN,
245 }
246 }
247}
248
249const fn key_index(key: Key) -> usize {
250 let first = (key[0] - b'A') as usize;
251
252 let second = if key[1] == 0 {
253 0
254 } else {
255 (key[1] - b'a' + 1) as usize
256 };
257
258 first * SECOND_LETTER_OPTIONS + second
259}
260
261fn element_from_key(key: Key) -> Option<Element> {
262 let atomic_number = ELEMENT_BY_KEY[key_index(key)];
263
264 if atomic_number == 0 {
265 None
266 } else {
267 Some(Element(atomic_number))
268 }
269}
270
271fn starts_with_letter(bytes: &[u8]) -> bool {
272 bytes.first().is_some_and(u8::is_ascii_alphabetic)
273}
274
275fn first_two_letter_element(bytes: &[u8]) -> Option<Element> {
276 bytes.get(..2).and_then(key_of).and_then(element_from_key)
277}
278
279const _: () = assert!((Element::MAX_ATOMIC_NUMBER as usize) < 128);
282
283impl fmt::Debug for Element {
284 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
285 if self.is_unknown() {
286 f.write_str("Element(unknown)")
287 } else {
288 write!(f, "Element({})", self.symbol())
289 }
290 }
291}
292
293impl fmt::Display for Element {
294 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
295 f.write_str(self.symbol())
296 }
297}
298
299#[cfg(test)]
300#[path = "table_tests.rs"]
301mod tests;