1use crate::document::{Category, CifValue, CifValueRef, DataBlock, Document};
12use crate::lexer::{LexError, Lexer, Quoting};
13use molframe_core::diagnostic::{Code, Diagnostic};
14use molframe_core::io::InputBuffer;
15use molframe_core::span::{ByteSpan, Position};
16use std::borrow::Cow;
17use std::collections::HashSet;
18use std::sync::Arc;
19
20mod state;
21use self::state::ParseState;
22
23pub type ParseResult = Result<(Document, Vec<Diagnostic>), Vec<Diagnostic>>;
25
26#[doc(hidden)]
31#[derive(Clone, Copy, Debug, PartialEq)]
32pub enum CifScalar<'input> {
33 Inapplicable,
35 Unknown,
37 Text(&'input str),
39 Integer(i64),
41 Float(f64),
43}
44
45#[doc(hidden)]
50#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
51#[repr(transparent)]
52pub(crate) struct ColumnId(u32);
53
54impl ColumnId {
55 pub(crate) fn from_position(position: usize) -> Option<Self> {
56 u32::try_from(position).ok().map(Self)
57 }
58
59 #[must_use]
61 pub(crate) const fn position(self) -> usize {
62 self.0 as usize
63 }
64}
65
66impl<'input> From<CifValueRef<'input>> for CifScalar<'input> {
67 fn from(value: CifValueRef<'input>) -> Self {
68 match value {
69 CifValueRef::Inapplicable => Self::Inapplicable,
70 CifValueRef::Unknown => Self::Unknown,
71 CifValueRef::Text(value) => Self::Text(value),
72 CifValueRef::Integer(value) => Self::Integer(value),
73 CifValueRef::Float(value) => Self::Float(value),
74 }
75 }
76}
77
78#[doc(hidden)]
106pub trait CifEventSink {
107 type Output;
109
110 fn block(&mut self, name: &str);
112
113 fn accepts_category(&self, _category: &str) -> bool {
115 true
116 }
117
118 fn begin_loop(&mut self, _tags: &[(Box<str>, Box<str>)]) {}
120
121 fn value(&mut self, category: &str, item: &str, value: CifScalar<'_>, span: ByteSpan);
123
124 fn end_row(&mut self) {}
126
127 fn end_loop(&mut self) {}
129
130 fn finish(self) -> Self::Output;
132}
133
134pub fn parse(input: &InputBuffer) -> ParseResult {
141 parse_into(input, DocumentSink::all())
142}
143
144#[doc(hidden)]
153pub fn parse_events<S>(
154 input: &InputBuffer,
155 sink: S,
156) -> Result<(S::Output, Vec<Diagnostic>), Vec<Diagnostic>>
157where
158 S: CifEventSink,
159{
160 parse_into(input, EventAdapter(sink))
161}
162
163struct EventAdapter<S>(S);
164
165impl<'input, S> ValueSink<'input> for EventAdapter<S>
166where
167 S: CifEventSink,
168{
169 type Output = S::Output;
170
171 fn block(&mut self, name: &str) {
172 self.0.block(name);
173 }
174
175 fn begin_loop(&mut self, tags: &[(Box<str>, Box<str>)]) {
176 self.0.begin_loop(tags);
177 }
178
179 fn value(
180 &mut self,
181 _column: ColumnId,
182 category: &str,
183 item: &str,
184 text: &'input str,
185 quoting: Quoting,
186 span: ByteSpan,
187 ) {
188 if !self.0.accepts_category(category) {
189 return;
190 }
191 self.0.value(
192 category,
193 item,
194 CifScalar::from(CifValueRef::parse(text, quoting)),
195 span,
196 );
197 }
198
199 fn end_row(&mut self) {
200 self.0.end_row();
201 }
202
203 fn end_loop(&mut self) {
204 self.0.end_loop();
205 }
206
207 fn finish(self) -> Self::Output {
208 self.0.finish()
209 }
210}
211
212pub(crate) trait ValueSink<'input> {
213 type Output;
214
215 fn block(&mut self, name: &str);
216
217 fn begin_loop(&mut self, _tags: &[(Box<str>, Box<str>)]) {}
218
219 fn value(
220 &mut self,
221 column: ColumnId,
222 category: &str,
223 item: &str,
224 text: &'input str,
225 quoting: Quoting,
226 span: ByteSpan,
227 );
228
229 fn end_row(&mut self) {}
230
231 fn end_loop(&mut self) {}
232
233 fn bulk_rows(&mut self, _tags: &[(Box<str>, Box<str>)], _rest: &'input str) -> Option<usize> {
240 None
241 }
242
243 fn finish(self) -> Self::Output;
244}
245
246pub(crate) fn parse_into<'input, S>(
247 input: &'input InputBuffer,
248 sink: S,
249) -> Result<(S::Output, Vec<Diagnostic>), Vec<Diagnostic>>
250where
251 S: ValueSink<'input>,
252{
253 let mut lexer = match Lexer::new(input.as_bytes()) {
254 Ok(lexer) => lexer,
255 Err(error) => return Err(vec![lex_finding(error)]),
256 };
257 let mut state = ParseState::new(sink);
258
259 loop {
260 match lexer.next_token() {
261 Ok(Some(spanned)) => {
262 state.token(spanned);
263 if state.offer_bulk {
264 state.offer_bulk = false;
265 let consumed = state.sink.bulk_rows(&state.loop_tags, lexer.rest());
266 if let Some(consumed) = consumed {
267 lexer.skip(consumed);
268 }
269 }
270 }
271 Ok(None) => break,
272 Err(error) => {
273 state.findings.push(lex_finding(error));
274 return Err(state.findings.finish());
275 }
276 }
277 }
278 state.finish()
279}
280
281pub(crate) struct DocumentSink {
282 document: Document,
283 texts: TextInterner,
284 keep: fn(&str) -> bool,
285}
286
287impl DocumentSink {
288 fn all() -> Self {
289 Self::with_filter(|_| true)
290 }
291
292 pub(crate) fn with_filter(keep: fn(&str) -> bool) -> Self {
293 Self {
294 document: Document::new(),
295 texts: TextInterner::default(),
296 keep,
297 }
298 }
299
300 pub(crate) fn block(&mut self, name: &str) {
301 self.document.push(DataBlock::new(name));
302 }
303
304 pub(crate) fn value(
305 &mut self,
306 category: &str,
307 item: &str,
308 text: &str,
309 quoting: Quoting,
310 span: ByteSpan,
311 ) {
312 if !(self.keep)(category) {
313 return;
314 }
315 let Some(block) = self.document.last_block_mut() else {
316 return;
317 };
318 block.category_mut(category, span).column_mut(item).push(
319 CifValue::parse_with_text(text, quoting, |text| self.texts.intern(text)),
320 quoting,
321 );
322 }
323
324 pub(crate) fn finish(self) -> Document {
325 self.document
326 }
327
328 pub(crate) const fn document(&self) -> &Document {
329 &self.document
330 }
331}
332
333impl<'input> ValueSink<'input> for DocumentSink {
334 type Output = Document;
335
336 fn block(&mut self, name: &str) {
337 Self::block(self, name);
338 }
339
340 fn value(
341 &mut self,
342 _column: ColumnId,
343 category: &str,
344 item: &str,
345 text: &'input str,
346 quoting: Quoting,
347 span: ByteSpan,
348 ) {
349 Self::value(self, category, item, text, quoting, span);
350 }
351
352 fn finish(self) -> Self::Output {
353 Self::finish(self)
354 }
355}
356
357#[must_use]
362pub fn split_tag(tag: &str) -> (&str, &str) {
363 let body = match tag.strip_prefix('_') {
364 Some(body) => body,
365 None => tag,
366 };
367 match body.split_once('.') {
368 Some((category, item)) => (category, item),
369 None => (body, body),
370 }
371}
372
373#[derive(Default)]
374struct TextInterner {
375 values: HashSet<Arc<str>>,
376}
377
378impl TextInterner {
379 fn intern(&mut self, text: &str) -> Arc<str> {
380 if let Some(value) = self.values.get(text) {
381 return Arc::clone(value);
382 }
383 let value: Arc<str> = text.into();
384 self.values.insert(Arc::clone(&value));
385 value
386 }
387}
388
389fn lex_finding(error: LexError) -> Diagnostic {
391 match error {
392 LexError::UnterminatedQuote(at) => Diagnostic::new(Code::E1101).at(ByteSpan::empty(at)),
393 LexError::UnterminatedText(at) => Diagnostic::new(Code::E1102).at(ByteSpan::empty(at)),
394 LexError::NotText => Diagnostic::new(Code::E1201)
395 .with_message("input is not valid text")
396 .at(ByteSpan::empty(Position::START)),
397 LexError::PositionOverflow(at) => Diagnostic::new(Code::E1201)
398 .with_message("input exceeds the supported source-position range")
399 .at(ByteSpan::empty(at)),
400 }
401}
402
403#[derive(Clone, Copy, Debug)]
409pub struct Rows<'a> {
410 category: &'a Category,
411 row: usize,
412}
413
414impl<'a> Rows<'a> {
415 #[must_use]
417 pub const fn new(category: &'a Category) -> Self {
418 Self { category, row: 0 }
419 }
420
421 #[must_use]
423 pub const fn category(&self) -> &'a Category {
424 self.category
425 }
426
427 #[must_use]
429 pub const fn row(&self) -> usize {
430 self.row
431 }
432
433 pub fn advance(&mut self) -> bool {
435 self.row += 1;
436 self.row < self.category.row_count()
437 }
438
439 #[must_use]
441 pub fn text(&self, item: &str) -> Option<&'a str> {
442 self.category.value(item, self.row)?.as_str()
443 }
444
445 #[must_use]
451 pub fn identifier(&self, item: &str) -> Option<Cow<'a, str>> {
452 self.category.value(item, self.row)?.as_identifier()
453 }
454
455 #[must_use]
457 pub fn integer(&self, item: &str) -> Option<i64> {
458 self.category.value(item, self.row)?.as_integer()
459 }
460
461 #[must_use]
463 pub fn float(&self, item: &str) -> Option<f64> {
464 self.category.value(item, self.row)?.as_float()
465 }
466
467 #[must_use]
469 pub fn is_recorded(&self, item: &str) -> bool {
470 self.category
471 .value(item, self.row)
472 .is_some_and(CifValue::is_recorded)
473 }
474}
475
476#[cfg(test)]
477#[path = "document_tests.rs"]
478mod tests;