1use super::number::fixed_point;
15use crate::lexer::Quoting;
16use indexmap::IndexMap;
17use molframe_core::span::ByteSpan;
18use num_traits::ToPrimitive;
19use std::borrow::Cow;
20use std::sync::Arc;
21
22#[derive(Clone, PartialEq, Debug)]
24pub enum CifValue {
25 Inapplicable,
27 Unknown,
29 Text(Arc<str>),
31 Integer(i64),
37 Float(f64),
39}
40
41fn numeric_lead(text: &str) -> bool {
44 let digits = match text.as_bytes() {
45 [b'+' | b'-', rest @ ..] => rest,
46 all => all,
47 };
48 matches!(digits.first(), Some(b'0'..=b'9' | b'.'))
49}
50
51fn spells_integer(text: &str, value: i64) -> bool {
56 let digits = match text.strip_prefix('-') {
57 Some(digits) => digits,
58 None => text,
59 };
60 let canonical = !digits.starts_with('+') && (digits == "0" || !digits.starts_with('0'));
61 canonical && (value != 0 || text == "0")
62}
63
64#[derive(Clone, Copy)]
65pub(crate) enum CifValueRef<'a> {
66 Inapplicable,
67 Unknown,
68 Text(&'a str),
69 Integer(i64),
70 Float(f64),
71}
72
73impl<'a> CifValueRef<'a> {
74 pub(crate) fn parse(text: &'a str, quoting: Quoting) -> Self {
75 if quoting != Quoting::Bare {
76 return Self::Text(text);
77 }
78 match text {
79 "." => Self::Inapplicable,
80 "?" => Self::Unknown,
81 _ => {
82 if text.as_bytes().contains(&b'.') {
83 if let Some(value) = fixed_point(text) {
84 return Self::Float(value);
85 }
86 if numeric_lead(text)
87 && let Ok(value) = text.parse::<f64>()
88 {
89 return Self::Float(value);
90 }
91 } else if let Ok(value) = text.parse::<i64>()
92 && spells_integer(text, value)
93 {
94 return Self::Integer(value);
99 }
100 Self::Text(text)
101 }
102 }
103 }
104
105 pub(crate) const fn as_str(self) -> Option<&'a str> {
106 match self {
107 Self::Text(text) => Some(text),
108 _ => None,
109 }
110 }
111
112 pub(crate) fn as_identifier(self) -> Option<Cow<'a, str>> {
113 match self {
114 Self::Text(text) => Some(Cow::Borrowed(text)),
115 Self::Integer(value) => Some(Cow::Owned(value.to_string())),
116 Self::Float(value) => Some(Cow::Owned(value.to_string())),
117 Self::Inapplicable | Self::Unknown => None,
118 }
119 }
120
121 pub(crate) fn as_integer(self) -> Option<i64> {
122 match self {
123 Self::Integer(value) => Some(value),
124 Self::Text(text) if numeric_lead(text) => text.parse().ok(),
125 _ => None,
126 }
127 }
128
129 pub(crate) fn as_float(self) -> Option<f64> {
130 match self {
131 Self::Integer(value) => value.to_f64(),
132 Self::Float(value) => Some(value),
133 Self::Text(text) if numeric_lead(text) => text.parse().ok(),
134 _ => None,
135 }
136 }
137
138 pub(crate) const fn is_recorded(self) -> bool {
139 !matches!(self, Self::Inapplicable | Self::Unknown)
140 }
141}
142
143impl CifValue {
144 #[must_use]
150 pub fn parse(text: &str, quoting: Quoting) -> Self {
151 Self::parse_with_text(text, quoting, |text| Arc::from(text))
152 }
153
154 pub(crate) fn parse_with_text(
155 text: &str,
156 quoting: Quoting,
157 retain: impl FnOnce(&str) -> Arc<str>,
158 ) -> Self {
159 match CifValueRef::parse(text, quoting) {
160 CifValueRef::Inapplicable => Self::Inapplicable,
161 CifValueRef::Unknown => Self::Unknown,
162 CifValueRef::Text(text) => Self::Text(retain(text)),
163 CifValueRef::Integer(value) => Self::Integer(value),
164 CifValueRef::Float(value) => Self::Float(value),
165 }
166 }
167
168 #[must_use]
170 pub fn as_str(&self) -> Option<&str> {
171 match self {
172 Self::Text(text) => Some(text),
173 _ => None,
174 }
175 }
176
177 #[must_use]
183 pub fn as_identifier(&self) -> Option<Cow<'_, str>> {
184 match self {
185 Self::Text(text) => Some(Cow::Borrowed(text)),
186 Self::Integer(value) => Some(Cow::Owned(value.to_string())),
187 Self::Float(value) => Some(Cow::Owned(value.to_string())),
188 Self::Inapplicable | Self::Unknown => None,
189 }
190 }
191
192 #[must_use]
197 pub fn as_integer(&self) -> Option<i64> {
198 match self {
199 Self::Integer(value) => Some(*value),
200 Self::Text(text) if numeric_lead(text) => text.parse().ok(),
201 _ => None,
202 }
203 }
204
205 #[must_use]
207 pub fn as_float(&self) -> Option<f64> {
208 match self {
209 Self::Integer(value) => value.to_f64(),
210 Self::Float(value) => Some(*value),
211 Self::Text(text) if numeric_lead(text) => text.parse().ok(),
212 _ => None,
213 }
214 }
215
216 #[must_use]
218 pub const fn is_recorded(&self) -> bool {
219 !matches!(self, Self::Inapplicable | Self::Unknown)
220 }
221}
222
223#[derive(Clone, Debug, Default)]
225pub struct Column {
226 values: Vec<CifValue>,
227 quoting: Vec<Quoting>,
228}
229
230impl Column {
231 pub fn reserve(&mut self, additional: usize) {
233 self.values.reserve(additional);
234 self.quoting.reserve(additional);
235 }
236
237 pub fn push(&mut self, value: CifValue, quoting: Quoting) {
239 self.values.push(value);
240 self.quoting.push(quoting);
241 }
242
243 #[must_use]
245 pub fn len(&self) -> usize {
246 self.values.len()
247 }
248
249 #[must_use]
251 pub fn is_empty(&self) -> bool {
252 self.values.is_empty()
253 }
254
255 #[must_use]
257 pub fn get(&self, row: usize) -> Option<&CifValue> {
258 self.values.get(row)
259 }
260
261 #[must_use]
264 pub fn quoting(&self, row: usize) -> Option<Quoting> {
265 self.quoting.get(row).copied()
266 }
267
268 pub fn iter(&self) -> impl Iterator<Item = &CifValue> {
270 self.values.iter()
271 }
272}
273
274#[derive(Clone, Debug)]
276pub struct Category {
277 name: Box<str>,
278 columns: IndexMap<Box<str>, Column>,
279 span: ByteSpan,
280}
281
282impl Category {
283 #[must_use]
285 pub fn new(name: impl Into<Box<str>>, span: ByteSpan) -> Self {
286 Self {
287 name: name.into(),
288 columns: IndexMap::new(),
289 span,
290 }
291 }
292
293 #[must_use]
295 pub fn name(&self) -> &str {
296 &self.name
297 }
298
299 #[must_use]
301 pub const fn span(&self) -> ByteSpan {
302 self.span
303 }
304
305 #[must_use]
307 pub fn row_count(&self) -> usize {
308 let Some(rows) = self.columns.values().map(Column::len).max() else {
310 return 0;
311 };
312 rows
313 }
314
315 #[must_use]
317 pub fn len(&self) -> usize {
318 self.columns.len()
319 }
320
321 #[must_use]
323 pub fn is_empty(&self) -> bool {
324 self.columns.is_empty()
325 }
326
327 #[must_use]
329 pub fn column(&self, item: &str) -> Option<&Column> {
330 self.columns.get(item)
331 }
332
333 pub fn column_mut(&mut self, item: &str) -> &mut Column {
335 if let Some(index) = self.columns.get_index_of(item) {
336 return &mut self.columns[index];
337 }
338 let (index, _) = self.columns.insert_full(item.into(), Column::default());
339 &mut self.columns[index]
340 }
341
342 pub fn items(&self) -> impl Iterator<Item = &str> {
344 self.columns.keys().map(Box::as_ref)
345 }
346
347 #[must_use]
349 pub fn value(&self, item: &str, row: usize) -> Option<&CifValue> {
350 self.column(item)?.get(row)
351 }
352
353 #[must_use]
355 pub fn text(&self, item: &str, row: usize) -> Option<&str> {
356 self.value(item, row)?.as_str()
357 }
358
359 #[must_use]
361 pub fn identifier(&self, item: &str, row: usize) -> Option<Cow<'_, str>> {
362 self.value(item, row)?.as_identifier()
363 }
364}
365
366#[derive(Clone, Debug)]
368pub struct DataBlock {
369 name: Box<str>,
370 categories: IndexMap<Box<str>, Category>,
371}
372
373impl DataBlock {
374 #[must_use]
376 pub fn new(name: impl Into<Box<str>>) -> Self {
377 Self {
378 name: name.into(),
379 categories: IndexMap::new(),
380 }
381 }
382
383 #[must_use]
385 pub fn name(&self) -> &str {
386 &self.name
387 }
388
389 #[must_use]
391 pub fn category(&self, name: &str) -> Option<&Category> {
392 self.categories.get(name)
393 }
394
395 pub fn category_mut(&mut self, name: &str, span: ByteSpan) -> &mut Category {
397 if let Some(index) = self.categories.get_index_of(name) {
398 return &mut self.categories[index];
399 }
400 let (index, _) = self
401 .categories
402 .insert_full(name.into(), Category::new(name, span));
403 &mut self.categories[index]
404 }
405
406 pub fn take_category(&mut self, name: &str) -> Option<Category> {
410 self.categories.shift_remove(name)
411 }
412
413 pub fn categories(&self) -> impl Iterator<Item = &Category> {
415 self.categories.values()
416 }
417
418 #[must_use]
420 pub fn len(&self) -> usize {
421 self.categories.len()
422 }
423
424 #[must_use]
426 pub fn is_empty(&self) -> bool {
427 self.categories.is_empty()
428 }
429}
430
431#[derive(Clone, Debug, Default)]
448pub struct Document {
449 blocks: Vec<DataBlock>,
450}
451
452impl Document {
453 #[must_use]
455 pub const fn new() -> Self {
456 Self { blocks: Vec::new() }
457 }
458
459 pub fn push(&mut self, block: DataBlock) {
461 self.blocks.push(block);
462 }
463
464 #[must_use]
466 pub fn first_block(&self) -> Option<&DataBlock> {
467 self.blocks.first()
468 }
469
470 pub fn last_block_mut(&mut self) -> Option<&mut DataBlock> {
472 self.blocks.last_mut()
473 }
474
475 pub fn blocks(&self) -> impl Iterator<Item = &DataBlock> {
477 self.blocks.iter()
478 }
479
480 pub fn blocks_mut(&mut self) -> impl Iterator<Item = &mut DataBlock> {
482 self.blocks.iter_mut()
483 }
484
485 #[must_use]
487 pub fn len(&self) -> usize {
488 self.blocks.len()
489 }
490
491 #[must_use]
493 pub fn is_empty(&self) -> bool {
494 self.blocks.is_empty()
495 }
496}
497
498#[cfg(test)]
499#[path = "model_tests.rs"]
500mod tests;