use super::{AtomBuilder, AtomSiteRow, Field};
use crate::lower::diagnostics::at_source_row;
use molframe_core::column::Presence;
use molframe_core::diagnostic::{Code, Diagnostic};
use molframe_core::element::Element;
use molframe_core::io::MissingElementPolicy;
use molframe_core::optional::OptionalSymbol;
use molframe_core::symbol::{AltId, SymbolId};
use num_traits::ToPrimitive;
pub(super) const UNSET_SYMBOL: SymbolId = SymbolId::from_raw(u32::MAX);
impl AtomBuilder<'_> {
pub(super) fn position_of<R: AtomSiteRow + ?Sized>(&mut self, rows: &R) -> Option<[f32; 3]> {
let (Some(x), Some(y), Some(z)) = (
rows.float(Field::CartnX),
rows.float(Field::CartnY),
rows.float(Field::CartnZ),
) else {
self.findings.push(at_source_row(
Diagnostic::new(Code::E1202)
.with_message("a coordinate could not be read")
.in_category("atom_site"),
rows.row(),
));
return None;
};
let Some(position) = x
.to_f32()
.zip(y.to_f32())
.zip(z.to_f32())
.map(|((x, y), z)| [x, y, z])
else {
self.findings.push(at_source_row(
Diagnostic::new(Code::E1202)
.with_message("a coordinate is outside the supported floating-point range")
.in_category("atom_site"),
rows.row(),
));
return None;
};
Some(position)
}
pub(super) fn optional_float<R: AtomSiteRow + ?Sized>(
&mut self,
rows: &R,
field: Field,
when_absent: f32,
) -> (f32, Presence) {
let Some(value) = rows.float(field) else {
return (when_absent, Presence::Unknown);
};
if let Some(value) = value.to_f32() {
return (value, Presence::Present);
}
self.findings.push(at_source_row(
Diagnostic::new(Code::E1202)
.with_message("an atom value is outside the supported floating-point range")
.in_category("atom_site")
.with_context("item", field.item()),
rows.row(),
));
(when_absent, Presence::Unknown)
}
pub(super) fn element_of<R: AtomSiteRow + ?Sized>(&mut self, rows: &R) -> Element {
if let Some(element) = rows.text(Field::TypeSymbol).and_then(Element::from_symbol) {
return element;
}
let name = rows.identifier(Field::LabelAtomId);
let inferred = match (self.options.missing_element_policy, name.as_deref()) {
(MissingElementPolicy::InferFromAtomName, Some(name)) => Element::infer_from_name(name),
_ => Element::UNKNOWN,
};
self.findings.push(at_source_row(
Diagnostic::new(Code::W3203)
.in_category("atom_site")
.with_context("inferred", inferred.symbol()),
rows.row(),
));
inferred
}
pub(super) fn auth_name_of<R: AtomSiteRow + ?Sized>(&mut self, rows: &R) -> OptionalSymbol {
self.symbol_of(rows, Field::AuthAtomId)
.map_or(OptionalSymbol::NONE, OptionalSymbol::some)
}
pub(super) fn alt_of<R: AtomSiteRow + ?Sized>(&mut self, rows: &R) -> Option<AltId> {
if let Some(text) = rows.identifier(Field::LabelAltId)
&& !text.is_empty()
{
return AltId::labelled(self.intern(&text));
}
Some(AltId::BLANK)
}
pub(super) fn symbol_of<R: AtomSiteRow + ?Sized>(
&mut self,
rows: &R,
field: Field,
) -> Option<SymbolId> {
let slot = rows
.dictionary_slot(field)
.and_then(|slot| usize::try_from(slot).ok());
if let Some(slot) = slot
&& let Some(symbol) = self.symbol_cache[field.position()].get(slot)
&& *symbol != UNSET_SYMBOL
{
return Some(*symbol);
}
let text = rows.identifier(field)?;
let symbol = self.intern(&text);
if let Some(slot) = slot {
let cache = &mut self.symbol_cache[field.position()];
if cache.len() <= slot {
cache.resize(slot + 1, UNSET_SYMBOL);
}
cache[slot] = symbol;
}
Some(symbol)
}
pub(super) fn symbol_or_empty<R: AtomSiteRow + ?Sized>(
&mut self,
rows: &R,
field: Field,
) -> SymbolId {
match self.symbol_of(rows, field) {
Some(symbol) => symbol,
None => self.intern(""),
}
}
pub(super) fn intern(&mut self, text: &str) -> SymbolId {
let Ok(symbol) = self.data.dictionary.intern(text) else {
self.findings
.push(Diagnostic::new(Code::E1901).with_message("the dictionary is full"));
return SymbolId::from_raw(0);
};
symbol
}
}