use super::number::fixed_point;
use crate::lexer::Quoting;
use indexmap::IndexMap;
use molframe_core::span::ByteSpan;
use num_traits::ToPrimitive;
use std::borrow::Cow;
use std::sync::Arc;
#[derive(Clone, PartialEq, Debug)]
pub enum CifValue {
Inapplicable,
Unknown,
Text(Arc<str>),
Integer(i64),
Float(f64),
}
fn numeric_lead(text: &str) -> bool {
let digits = match text.as_bytes() {
[b'+' | b'-', rest @ ..] => rest,
all => all,
};
matches!(digits.first(), Some(b'0'..=b'9' | b'.'))
}
fn spells_integer(text: &str, value: i64) -> bool {
let digits = match text.strip_prefix('-') {
Some(digits) => digits,
None => text,
};
let canonical = !digits.starts_with('+') && (digits == "0" || !digits.starts_with('0'));
canonical && (value != 0 || text == "0")
}
#[derive(Clone, Copy)]
pub(crate) enum CifValueRef<'a> {
Inapplicable,
Unknown,
Text(&'a str),
Integer(i64),
Float(f64),
}
impl<'a> CifValueRef<'a> {
pub(crate) fn parse(text: &'a str, quoting: Quoting) -> Self {
if quoting != Quoting::Bare {
return Self::Text(text);
}
match text {
"." => Self::Inapplicable,
"?" => Self::Unknown,
_ => {
if text.as_bytes().contains(&b'.') {
if let Some(value) = fixed_point(text) {
return Self::Float(value);
}
if numeric_lead(text)
&& let Ok(value) = text.parse::<f64>()
{
return Self::Float(value);
}
} else if let Ok(value) = text.parse::<i64>()
&& spells_integer(text, value)
{
return Self::Integer(value);
}
Self::Text(text)
}
}
}
pub(crate) const fn as_str(self) -> Option<&'a str> {
match self {
Self::Text(text) => Some(text),
_ => None,
}
}
pub(crate) fn as_identifier(self) -> Option<Cow<'a, str>> {
match self {
Self::Text(text) => Some(Cow::Borrowed(text)),
Self::Integer(value) => Some(Cow::Owned(value.to_string())),
Self::Float(value) => Some(Cow::Owned(value.to_string())),
Self::Inapplicable | Self::Unknown => None,
}
}
pub(crate) fn as_integer(self) -> Option<i64> {
match self {
Self::Integer(value) => Some(value),
Self::Text(text) if numeric_lead(text) => text.parse().ok(),
_ => None,
}
}
pub(crate) fn as_float(self) -> Option<f64> {
match self {
Self::Integer(value) => value.to_f64(),
Self::Float(value) => Some(value),
Self::Text(text) if numeric_lead(text) => text.parse().ok(),
_ => None,
}
}
pub(crate) const fn is_recorded(self) -> bool {
!matches!(self, Self::Inapplicable | Self::Unknown)
}
}
impl CifValue {
#[must_use]
pub fn parse(text: &str, quoting: Quoting) -> Self {
Self::parse_with_text(text, quoting, |text| Arc::from(text))
}
pub(crate) fn parse_with_text(
text: &str,
quoting: Quoting,
retain: impl FnOnce(&str) -> Arc<str>,
) -> Self {
match CifValueRef::parse(text, quoting) {
CifValueRef::Inapplicable => Self::Inapplicable,
CifValueRef::Unknown => Self::Unknown,
CifValueRef::Text(text) => Self::Text(retain(text)),
CifValueRef::Integer(value) => Self::Integer(value),
CifValueRef::Float(value) => Self::Float(value),
}
}
#[must_use]
pub fn as_str(&self) -> Option<&str> {
match self {
Self::Text(text) => Some(text),
_ => None,
}
}
#[must_use]
pub fn as_identifier(&self) -> Option<Cow<'_, str>> {
match self {
Self::Text(text) => Some(Cow::Borrowed(text)),
Self::Integer(value) => Some(Cow::Owned(value.to_string())),
Self::Float(value) => Some(Cow::Owned(value.to_string())),
Self::Inapplicable | Self::Unknown => None,
}
}
#[must_use]
pub fn as_integer(&self) -> Option<i64> {
match self {
Self::Integer(value) => Some(*value),
Self::Text(text) if numeric_lead(text) => text.parse().ok(),
_ => None,
}
}
#[must_use]
pub fn as_float(&self) -> Option<f64> {
match self {
Self::Integer(value) => value.to_f64(),
Self::Float(value) => Some(*value),
Self::Text(text) if numeric_lead(text) => text.parse().ok(),
_ => None,
}
}
#[must_use]
pub const fn is_recorded(&self) -> bool {
!matches!(self, Self::Inapplicable | Self::Unknown)
}
}
#[derive(Clone, Debug, Default)]
pub struct Column {
values: Vec<CifValue>,
quoting: Vec<Quoting>,
}
impl Column {
pub fn reserve(&mut self, additional: usize) {
self.values.reserve(additional);
self.quoting.reserve(additional);
}
pub fn push(&mut self, value: CifValue, quoting: Quoting) {
self.values.push(value);
self.quoting.push(quoting);
}
#[must_use]
pub fn len(&self) -> usize {
self.values.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.values.is_empty()
}
#[must_use]
pub fn get(&self, row: usize) -> Option<&CifValue> {
self.values.get(row)
}
#[must_use]
pub fn quoting(&self, row: usize) -> Option<Quoting> {
self.quoting.get(row).copied()
}
pub fn iter(&self) -> impl Iterator<Item = &CifValue> {
self.values.iter()
}
}
#[derive(Clone, Debug)]
pub struct Category {
name: Box<str>,
columns: IndexMap<Box<str>, Column>,
span: ByteSpan,
}
impl Category {
#[must_use]
pub fn new(name: impl Into<Box<str>>, span: ByteSpan) -> Self {
Self {
name: name.into(),
columns: IndexMap::new(),
span,
}
}
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
#[must_use]
pub const fn span(&self) -> ByteSpan {
self.span
}
#[must_use]
pub fn row_count(&self) -> usize {
let Some(rows) = self.columns.values().map(Column::len).max() else {
return 0;
};
rows
}
#[must_use]
pub fn len(&self) -> usize {
self.columns.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.columns.is_empty()
}
#[must_use]
pub fn column(&self, item: &str) -> Option<&Column> {
self.columns.get(item)
}
pub fn column_mut(&mut self, item: &str) -> &mut Column {
if let Some(index) = self.columns.get_index_of(item) {
return &mut self.columns[index];
}
let (index, _) = self.columns.insert_full(item.into(), Column::default());
&mut self.columns[index]
}
pub fn items(&self) -> impl Iterator<Item = &str> {
self.columns.keys().map(Box::as_ref)
}
#[must_use]
pub fn value(&self, item: &str, row: usize) -> Option<&CifValue> {
self.column(item)?.get(row)
}
#[must_use]
pub fn text(&self, item: &str, row: usize) -> Option<&str> {
self.value(item, row)?.as_str()
}
#[must_use]
pub fn identifier(&self, item: &str, row: usize) -> Option<Cow<'_, str>> {
self.value(item, row)?.as_identifier()
}
}
#[derive(Clone, Debug)]
pub struct DataBlock {
name: Box<str>,
categories: IndexMap<Box<str>, Category>,
}
impl DataBlock {
#[must_use]
pub fn new(name: impl Into<Box<str>>) -> Self {
Self {
name: name.into(),
categories: IndexMap::new(),
}
}
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
#[must_use]
pub fn category(&self, name: &str) -> Option<&Category> {
self.categories.get(name)
}
pub fn category_mut(&mut self, name: &str, span: ByteSpan) -> &mut Category {
if let Some(index) = self.categories.get_index_of(name) {
return &mut self.categories[index];
}
let (index, _) = self
.categories
.insert_full(name.into(), Category::new(name, span));
&mut self.categories[index]
}
pub fn take_category(&mut self, name: &str) -> Option<Category> {
self.categories.shift_remove(name)
}
pub fn categories(&self) -> impl Iterator<Item = &Category> {
self.categories.values()
}
#[must_use]
pub fn len(&self) -> usize {
self.categories.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.categories.is_empty()
}
}
#[derive(Clone, Debug, Default)]
pub struct Document {
blocks: Vec<DataBlock>,
}
impl Document {
#[must_use]
pub const fn new() -> Self {
Self { blocks: Vec::new() }
}
pub fn push(&mut self, block: DataBlock) {
self.blocks.push(block);
}
#[must_use]
pub fn first_block(&self) -> Option<&DataBlock> {
self.blocks.first()
}
pub fn last_block_mut(&mut self) -> Option<&mut DataBlock> {
self.blocks.last_mut()
}
pub fn blocks(&self) -> impl Iterator<Item = &DataBlock> {
self.blocks.iter()
}
pub fn blocks_mut(&mut self) -> impl Iterator<Item = &mut DataBlock> {
self.blocks.iter_mut()
}
#[must_use]
pub fn len(&self) -> usize {
self.blocks.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.blocks.is_empty()
}
}
#[cfg(test)]
#[path = "model_tests.rs"]
mod tests;