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
41#[derive(Clone, Copy)]
42pub(crate) enum CifValueRef<'a> {
43 Inapplicable,
44 Unknown,
45 Text(&'a str),
46 Integer(i64),
47 Float(f64),
48}
49
50impl<'a> CifValueRef<'a> {
51 pub(crate) fn parse(text: &'a str, quoting: Quoting) -> Self {
52 if quoting != Quoting::Bare {
53 return Self::Text(text);
54 }
55 match text {
56 "." => Self::Inapplicable,
57 "?" => Self::Unknown,
58 _ => {
59 let fractional = text
60 .as_bytes()
61 .iter()
62 .any(|byte| matches!(byte, b'.' | b'e' | b'E'));
63 if fractional {
64 if let Some(value) = fixed_point(text) {
65 return Self::Float(value);
66 }
67 if let Ok(value) = text.parse::<f64>() {
68 return Self::Float(value);
69 }
70 if let Ok(value) = text.parse::<i64>() {
71 return Self::Integer(value);
72 }
73 } else {
74 if let Ok(value) = text.parse::<i64>() {
75 return Self::Integer(value);
76 }
77 if let Ok(value) = text.parse::<f64>() {
78 return Self::Float(value);
79 }
80 }
81 Self::Text(text)
82 }
83 }
84 }
85
86 pub(crate) const fn as_str(self) -> Option<&'a str> {
87 match self {
88 Self::Text(text) => Some(text),
89 _ => None,
90 }
91 }
92
93 pub(crate) fn as_identifier(self) -> Option<Cow<'a, str>> {
94 match self {
95 Self::Text(text) => Some(Cow::Borrowed(text)),
96 Self::Integer(value) => Some(Cow::Owned(value.to_string())),
97 Self::Float(value) => Some(Cow::Owned(value.to_string())),
98 Self::Inapplicable | Self::Unknown => None,
99 }
100 }
101
102 pub(crate) const fn as_integer(self) -> Option<i64> {
103 match self {
104 Self::Integer(value) => Some(value),
105 _ => None,
106 }
107 }
108
109 pub(crate) fn as_float(self) -> Option<f64> {
110 match self {
111 Self::Integer(value) => value.to_f64(),
112 Self::Float(value) => Some(value),
113 _ => None,
114 }
115 }
116
117 pub(crate) const fn is_recorded(self) -> bool {
118 !matches!(self, Self::Inapplicable | Self::Unknown)
119 }
120}
121
122impl CifValue {
123 #[must_use]
129 pub fn parse(text: &str, quoting: Quoting) -> Self {
130 Self::parse_with_text(text, quoting, |text| Arc::from(text))
131 }
132
133 pub(crate) fn parse_with_text(
134 text: &str,
135 quoting: Quoting,
136 retain: impl FnOnce(&str) -> Arc<str>,
137 ) -> Self {
138 match CifValueRef::parse(text, quoting) {
139 CifValueRef::Inapplicable => Self::Inapplicable,
140 CifValueRef::Unknown => Self::Unknown,
141 CifValueRef::Text(text) => Self::Text(retain(text)),
142 CifValueRef::Integer(value) => Self::Integer(value),
143 CifValueRef::Float(value) => Self::Float(value),
144 }
145 }
146
147 #[must_use]
149 pub fn as_str(&self) -> Option<&str> {
150 match self {
151 Self::Text(text) => Some(text),
152 _ => None,
153 }
154 }
155
156 #[must_use]
162 pub fn as_identifier(&self) -> Option<Cow<'_, str>> {
163 match self {
164 Self::Text(text) => Some(Cow::Borrowed(text)),
165 Self::Integer(value) => Some(Cow::Owned(value.to_string())),
166 Self::Float(value) => Some(Cow::Owned(value.to_string())),
167 Self::Inapplicable | Self::Unknown => None,
168 }
169 }
170
171 #[must_use]
173 pub fn as_integer(&self) -> Option<i64> {
174 match self {
175 Self::Integer(value) => Some(*value),
176 _ => None,
177 }
178 }
179
180 #[must_use]
182 pub fn as_float(&self) -> Option<f64> {
183 match self {
184 Self::Integer(value) => value.to_f64(),
185 Self::Float(value) => Some(*value),
186 _ => None,
187 }
188 }
189
190 #[must_use]
192 pub const fn is_recorded(&self) -> bool {
193 !matches!(self, Self::Inapplicable | Self::Unknown)
194 }
195}
196
197#[derive(Clone, Debug, Default)]
199pub struct Column {
200 values: Vec<CifValue>,
201 quoting: Vec<Quoting>,
202}
203
204impl Column {
205 pub fn reserve(&mut self, additional: usize) {
207 self.values.reserve(additional);
208 self.quoting.reserve(additional);
209 }
210
211 pub fn push(&mut self, value: CifValue, quoting: Quoting) {
213 self.values.push(value);
214 self.quoting.push(quoting);
215 }
216
217 #[must_use]
219 pub fn len(&self) -> usize {
220 self.values.len()
221 }
222
223 #[must_use]
225 pub fn is_empty(&self) -> bool {
226 self.values.is_empty()
227 }
228
229 #[must_use]
231 pub fn get(&self, row: usize) -> Option<&CifValue> {
232 self.values.get(row)
233 }
234
235 #[must_use]
238 pub fn quoting(&self, row: usize) -> Option<Quoting> {
239 self.quoting.get(row).copied()
240 }
241
242 pub fn iter(&self) -> impl Iterator<Item = &CifValue> {
244 self.values.iter()
245 }
246}
247
248#[derive(Clone, Debug)]
250pub struct Category {
251 name: Box<str>,
252 columns: IndexMap<Box<str>, Column>,
253 span: ByteSpan,
254}
255
256impl Category {
257 #[must_use]
259 pub fn new(name: impl Into<Box<str>>, span: ByteSpan) -> Self {
260 Self {
261 name: name.into(),
262 columns: IndexMap::new(),
263 span,
264 }
265 }
266
267 #[must_use]
269 pub fn name(&self) -> &str {
270 &self.name
271 }
272
273 #[must_use]
275 pub const fn span(&self) -> ByteSpan {
276 self.span
277 }
278
279 #[must_use]
281 pub fn row_count(&self) -> usize {
282 let Some(rows) = self.columns.values().map(Column::len).max() else {
284 return 0;
285 };
286 rows
287 }
288
289 #[must_use]
291 pub fn len(&self) -> usize {
292 self.columns.len()
293 }
294
295 #[must_use]
297 pub fn is_empty(&self) -> bool {
298 self.columns.is_empty()
299 }
300
301 #[must_use]
303 pub fn column(&self, item: &str) -> Option<&Column> {
304 self.columns.get(item)
305 }
306
307 pub fn column_mut(&mut self, item: &str) -> &mut Column {
309 if let Some(index) = self.columns.get_index_of(item) {
310 return &mut self.columns[index];
311 }
312 let (index, _) = self.columns.insert_full(item.into(), Column::default());
313 &mut self.columns[index]
314 }
315
316 pub fn items(&self) -> impl Iterator<Item = &str> {
318 self.columns.keys().map(Box::as_ref)
319 }
320
321 #[must_use]
323 pub fn value(&self, item: &str, row: usize) -> Option<&CifValue> {
324 self.column(item)?.get(row)
325 }
326
327 #[must_use]
329 pub fn text(&self, item: &str, row: usize) -> Option<&str> {
330 self.value(item, row)?.as_str()
331 }
332
333 #[must_use]
335 pub fn identifier(&self, item: &str, row: usize) -> Option<Cow<'_, str>> {
336 self.value(item, row)?.as_identifier()
337 }
338}
339
340#[derive(Clone, Debug)]
342pub struct DataBlock {
343 name: Box<str>,
344 categories: IndexMap<Box<str>, Category>,
345}
346
347impl DataBlock {
348 #[must_use]
350 pub fn new(name: impl Into<Box<str>>) -> Self {
351 Self {
352 name: name.into(),
353 categories: IndexMap::new(),
354 }
355 }
356
357 #[must_use]
359 pub fn name(&self) -> &str {
360 &self.name
361 }
362
363 #[must_use]
365 pub fn category(&self, name: &str) -> Option<&Category> {
366 self.categories.get(name)
367 }
368
369 pub fn category_mut(&mut self, name: &str, span: ByteSpan) -> &mut Category {
371 if let Some(index) = self.categories.get_index_of(name) {
372 return &mut self.categories[index];
373 }
374 let (index, _) = self
375 .categories
376 .insert_full(name.into(), Category::new(name, span));
377 &mut self.categories[index]
378 }
379
380 pub fn categories(&self) -> impl Iterator<Item = &Category> {
382 self.categories.values()
383 }
384
385 #[must_use]
387 pub fn len(&self) -> usize {
388 self.categories.len()
389 }
390
391 #[must_use]
393 pub fn is_empty(&self) -> bool {
394 self.categories.is_empty()
395 }
396}
397
398#[derive(Clone, Debug, Default)]
415pub struct Document {
416 blocks: Vec<DataBlock>,
417}
418
419impl Document {
420 #[must_use]
422 pub const fn new() -> Self {
423 Self { blocks: Vec::new() }
424 }
425
426 pub fn push(&mut self, block: DataBlock) {
428 self.blocks.push(block);
429 }
430
431 #[must_use]
433 pub fn first_block(&self) -> Option<&DataBlock> {
434 self.blocks.first()
435 }
436
437 pub fn last_block_mut(&mut self) -> Option<&mut DataBlock> {
439 self.blocks.last_mut()
440 }
441
442 pub fn blocks(&self) -> impl Iterator<Item = &DataBlock> {
444 self.blocks.iter()
445 }
446
447 #[must_use]
449 pub fn len(&self) -> usize {
450 self.blocks.len()
451 }
452
453 #[must_use]
455 pub fn is_empty(&self) -> bool {
456 self.blocks.is_empty()
457 }
458}
459
460#[cfg(test)]
461#[path = "model_tests.rs"]
462mod tests;