use std::fmt;
const SYMBOLS: [&str; 119] = [
"", "H", "He", "Li", "Be", "B", "C", "N", "O", "F", "Ne", "Na", "Mg", "Al", "Si", "P", "S",
"Cl", "Ar", "K", "Ca", "Sc", "Ti", "V", "Cr", "Mn", "Fe", "Co", "Ni", "Cu", "Zn", "Ga", "Ge",
"As", "Se", "Br", "Kr", "Rb", "Sr", "Y", "Zr", "Nb", "Mo", "Tc", "Ru", "Rh", "Pd", "Ag", "Cd",
"In", "Sn", "Sb", "Te", "I", "Xe", "Cs", "Ba", "La", "Ce", "Pr", "Nd", "Pm", "Sm", "Eu", "Gd",
"Tb", "Dy", "Ho", "Er", "Tm", "Yb", "Lu", "Hf", "Ta", "W", "Re", "Os", "Ir", "Pt", "Au", "Hg",
"Tl", "Pb", "Bi", "Po", "At", "Rn", "Fr", "Ra", "Ac", "Th", "Pa", "U", "Np", "Pu", "Am", "Cm",
"Bk", "Cf", "Es", "Fm", "Md", "No", "Lr", "Rf", "Db", "Sg", "Bh", "Hs", "Mt", "Ds", "Rg", "Cn",
"Nh", "Fl", "Mc", "Lv", "Ts", "Og",
];
type Key = [u8; 2];
const LETTERS: usize = 26;
const SECOND_LETTER_OPTIONS: usize = LETTERS + 1;
const KEY_COUNT: usize = LETTERS * SECOND_LETTER_OPTIONS;
const KEYS: [Key; 119] = build_keys();
const ELEMENT_BY_KEY: [u8; KEY_COUNT] = build_element_lookup();
const fn build_keys() -> [Key; 119] {
let mut keys = [[0u8; 2]; 119];
let mut z = 1;
while z < SYMBOLS.len() {
let bytes = SYMBOLS[z].as_bytes();
keys[z] = if bytes.len() == 1 {
[bytes[0], 0]
} else {
[bytes[0], bytes[1]]
};
z += 1;
}
keys
}
const fn build_element_lookup() -> [u8; KEY_COUNT] {
let mut lookup = [0u8; KEY_COUNT];
let mut z = 1u8;
while (z as usize) < KEYS.len() {
lookup[key_index(KEYS[z as usize])] = z;
z += 1;
}
lookup
}
const fn key_of(bytes: &[u8]) -> Option<Key> {
match bytes {
[a] if a.is_ascii_alphabetic() => Some([a.to_ascii_uppercase(), 0]),
[a, b] if a.is_ascii_alphabetic() && b.is_ascii_alphabetic() => {
Some([a.to_ascii_uppercase(), b.to_ascii_lowercase()])
}
_ => None,
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
#[repr(transparent)]
pub struct Element(u8);
impl Element {
pub const UNKNOWN: Self = Self(0);
pub const HYDROGEN: Self = Self(1);
pub const CARBON: Self = Self(6);
pub const NITROGEN: Self = Self(7);
pub const OXYGEN: Self = Self(8);
pub const FLUORINE: Self = Self(9);
pub const PHOSPHORUS: Self = Self(15);
pub const SULFUR: Self = Self(16);
pub const CHLORINE: Self = Self(17);
pub const CALCIUM: Self = Self(20);
pub const IRON: Self = Self(26);
pub const ZINC: Self = Self(30);
pub const SELENIUM: Self = Self(34);
pub const MAX_ATOMIC_NUMBER: u8 = 118;
#[must_use]
pub const fn from_atomic_number(z: u8) -> Self {
if z <= Self::MAX_ATOMIC_NUMBER {
Self(z)
} else {
Self::UNKNOWN
}
}
#[must_use]
pub const fn atomic_number(self) -> u8 {
self.0
}
#[must_use]
pub fn symbol(self) -> &'static str {
match SYMBOLS.get(usize::from(self.0)) {
Some(symbol) => symbol,
None => "",
}
}
#[must_use]
pub const fn is_unknown(self) -> bool {
self.0 == 0
}
#[must_use]
pub const fn is_hydrogen(self) -> bool {
self.0 == Self::HYDROGEN.0
}
#[must_use]
pub fn from_symbol(text: &str) -> Option<Self> {
let text = text.trim();
if text.eq_ignore_ascii_case("D") || text.eq_ignore_ascii_case("T") {
return Some(Self::HYDROGEN);
}
key_of(text.as_bytes()).and_then(element_from_key)
}
#[must_use]
pub fn infer_from_pdb_atom_name(field: &str) -> Self {
let bytes = field.as_bytes();
if starts_with_letter(bytes)
&& let Some(element) = first_two_letter_element(bytes)
{
return element;
}
Self::first_letter_element(bytes)
}
#[must_use]
pub fn infer_from_name(name: &str) -> Self {
let bytes = name.as_bytes();
if let Some(element) = first_two_letter_element(bytes) {
return element;
}
Self::first_letter_element(bytes)
}
fn first_letter_element(bytes: &[u8]) -> Self {
let Some(letter) = bytes.iter().copied().find(u8::is_ascii_alphabetic) else {
return Self::UNKNOWN;
};
match key_of(&[letter]).and_then(element_from_key) {
Some(element) => element,
None => Self::UNKNOWN,
}
}
}
const fn key_index(key: Key) -> usize {
let first = (key[0] - b'A') as usize;
let second = if key[1] == 0 {
0
} else {
(key[1] - b'a' + 1) as usize
};
first * SECOND_LETTER_OPTIONS + second
}
fn element_from_key(key: Key) -> Option<Element> {
let atomic_number = ELEMENT_BY_KEY[key_index(key)];
if atomic_number == 0 {
None
} else {
Some(Element(atomic_number))
}
}
fn starts_with_letter(bytes: &[u8]) -> bool {
bytes.first().is_some_and(u8::is_ascii_alphabetic)
}
fn first_two_letter_element(bytes: &[u8]) -> Option<Element> {
bytes.get(..2).and_then(key_of).and_then(element_from_key)
}
const _: () = assert!((Element::MAX_ATOMIC_NUMBER as usize) < 128);
impl fmt::Debug for Element {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_unknown() {
f.write_str("Element(unknown)")
} else {
write!(f, "Element({})", self.symbol())
}
}
}
impl fmt::Display for Element {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.symbol())
}
}
#[cfg(test)]
#[path = "table_tests.rs"]
mod tests;