use crate::document::{Category, CifValue, CifValueRef, DataBlock, Document};
use crate::lexer::{LexError, Lexer, Quoting};
use molframe_core::diagnostic::{Code, Diagnostic};
use molframe_core::io::InputBuffer;
use molframe_core::span::{ByteSpan, Position};
use std::borrow::Cow;
use std::collections::HashSet;
use std::sync::Arc;
mod state;
use self::state::ParseState;
pub type ParseResult = Result<(Document, Vec<Diagnostic>), Vec<Diagnostic>>;
#[doc(hidden)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum CifScalar<'input> {
Inapplicable,
Unknown,
Text(&'input str),
Integer(i64),
Float(f64),
}
#[doc(hidden)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[repr(transparent)]
pub(crate) struct ColumnId(u32);
impl ColumnId {
pub(crate) fn from_position(position: usize) -> Option<Self> {
u32::try_from(position).ok().map(Self)
}
#[must_use]
pub(crate) const fn position(self) -> usize {
self.0 as usize
}
}
impl<'input> From<CifValueRef<'input>> for CifScalar<'input> {
fn from(value: CifValueRef<'input>) -> Self {
match value {
CifValueRef::Inapplicable => Self::Inapplicable,
CifValueRef::Unknown => Self::Unknown,
CifValueRef::Text(value) => Self::Text(value),
CifValueRef::Integer(value) => Self::Integer(value),
CifValueRef::Float(value) => Self::Float(value),
}
}
}
#[doc(hidden)]
pub trait CifEventSink {
type Output;
fn block(&mut self, name: &str);
fn accepts_category(&self, _category: &str) -> bool {
true
}
fn begin_loop(&mut self, _tags: &[(Box<str>, Box<str>)]) {}
fn value(&mut self, category: &str, item: &str, value: CifScalar<'_>, span: ByteSpan);
fn end_row(&mut self) {}
fn end_loop(&mut self) {}
fn finish(self) -> Self::Output;
}
pub fn parse(input: &InputBuffer) -> ParseResult {
parse_into(input, DocumentSink::all())
}
#[doc(hidden)]
pub fn parse_events<S>(
input: &InputBuffer,
sink: S,
) -> Result<(S::Output, Vec<Diagnostic>), Vec<Diagnostic>>
where
S: CifEventSink,
{
parse_into(input, EventAdapter(sink))
}
struct EventAdapter<S>(S);
impl<'input, S> ValueSink<'input> for EventAdapter<S>
where
S: CifEventSink,
{
type Output = S::Output;
fn block(&mut self, name: &str) {
self.0.block(name);
}
fn begin_loop(&mut self, tags: &[(Box<str>, Box<str>)]) {
self.0.begin_loop(tags);
}
fn value(
&mut self,
_column: ColumnId,
category: &str,
item: &str,
text: &'input str,
quoting: Quoting,
span: ByteSpan,
) {
if !self.0.accepts_category(category) {
return;
}
self.0.value(
category,
item,
CifScalar::from(CifValueRef::parse(text, quoting)),
span,
);
}
fn end_row(&mut self) {
self.0.end_row();
}
fn end_loop(&mut self) {
self.0.end_loop();
}
fn finish(self) -> Self::Output {
self.0.finish()
}
}
pub(crate) trait ValueSink<'input> {
type Output;
fn block(&mut self, name: &str);
fn begin_loop(&mut self, _tags: &[(Box<str>, Box<str>)]) {}
fn value(
&mut self,
column: ColumnId,
category: &str,
item: &str,
text: &'input str,
quoting: Quoting,
span: ByteSpan,
);
fn end_row(&mut self) {}
fn end_loop(&mut self) {}
fn bulk_rows(&mut self, _tags: &[(Box<str>, Box<str>)], _rest: &'input str) -> Option<usize> {
None
}
fn finish(self) -> Self::Output;
}
pub(crate) fn parse_into<'input, S>(
input: &'input InputBuffer,
sink: S,
) -> Result<(S::Output, Vec<Diagnostic>), Vec<Diagnostic>>
where
S: ValueSink<'input>,
{
let mut lexer = match Lexer::new(input.as_bytes()) {
Ok(lexer) => lexer,
Err(error) => return Err(vec![lex_finding(error)]),
};
let mut state = ParseState::new(sink);
loop {
match lexer.next_token() {
Ok(Some(spanned)) => {
state.token(spanned);
if state.offer_bulk {
state.offer_bulk = false;
let consumed = state.sink.bulk_rows(&state.loop_tags, lexer.rest());
if let Some(consumed) = consumed {
lexer.skip(consumed);
}
}
}
Ok(None) => break,
Err(error) => {
state.findings.push(lex_finding(error));
return Err(state.findings.finish());
}
}
}
state.finish()
}
pub(crate) struct DocumentSink {
document: Document,
texts: TextInterner,
keep: fn(&str) -> bool,
}
impl DocumentSink {
fn all() -> Self {
Self::with_filter(|_| true)
}
pub(crate) fn with_filter(keep: fn(&str) -> bool) -> Self {
Self {
document: Document::new(),
texts: TextInterner::default(),
keep,
}
}
pub(crate) fn block(&mut self, name: &str) {
self.document.push(DataBlock::new(name));
}
pub(crate) fn value(
&mut self,
category: &str,
item: &str,
text: &str,
quoting: Quoting,
span: ByteSpan,
) {
if !(self.keep)(category) {
return;
}
let Some(block) = self.document.last_block_mut() else {
return;
};
block.category_mut(category, span).column_mut(item).push(
CifValue::parse_with_text(text, quoting, |text| self.texts.intern(text)),
quoting,
);
}
pub(crate) fn finish(self) -> Document {
self.document
}
pub(crate) const fn document(&self) -> &Document {
&self.document
}
}
impl<'input> ValueSink<'input> for DocumentSink {
type Output = Document;
fn block(&mut self, name: &str) {
Self::block(self, name);
}
fn value(
&mut self,
_column: ColumnId,
category: &str,
item: &str,
text: &'input str,
quoting: Quoting,
span: ByteSpan,
) {
Self::value(self, category, item, text, quoting, span);
}
fn finish(self) -> Self::Output {
Self::finish(self)
}
}
#[must_use]
pub fn split_tag(tag: &str) -> (&str, &str) {
let body = match tag.strip_prefix('_') {
Some(body) => body,
None => tag,
};
match body.split_once('.') {
Some((category, item)) => (category, item),
None => (body, body),
}
}
#[derive(Default)]
struct TextInterner {
values: HashSet<Arc<str>>,
}
impl TextInterner {
fn intern(&mut self, text: &str) -> Arc<str> {
if let Some(value) = self.values.get(text) {
return Arc::clone(value);
}
let value: Arc<str> = text.into();
self.values.insert(Arc::clone(&value));
value
}
}
fn lex_finding(error: LexError) -> Diagnostic {
match error {
LexError::UnterminatedQuote(at) => Diagnostic::new(Code::E1101).at(ByteSpan::empty(at)),
LexError::UnterminatedText(at) => Diagnostic::new(Code::E1102).at(ByteSpan::empty(at)),
LexError::NotText => Diagnostic::new(Code::E1201)
.with_message("input is not valid text")
.at(ByteSpan::empty(Position::START)),
LexError::PositionOverflow(at) => Diagnostic::new(Code::E1201)
.with_message("input exceeds the supported source-position range")
.at(ByteSpan::empty(at)),
}
}
#[derive(Clone, Copy, Debug)]
pub struct Rows<'a> {
category: &'a Category,
row: usize,
}
impl<'a> Rows<'a> {
#[must_use]
pub const fn new(category: &'a Category) -> Self {
Self { category, row: 0 }
}
#[must_use]
pub const fn category(&self) -> &'a Category {
self.category
}
#[must_use]
pub const fn row(&self) -> usize {
self.row
}
pub fn advance(&mut self) -> bool {
self.row += 1;
self.row < self.category.row_count()
}
#[must_use]
pub fn text(&self, item: &str) -> Option<&'a str> {
self.category.value(item, self.row)?.as_str()
}
#[must_use]
pub fn identifier(&self, item: &str) -> Option<Cow<'a, str>> {
self.category.value(item, self.row)?.as_identifier()
}
#[must_use]
pub fn integer(&self, item: &str) -> Option<i64> {
self.category.value(item, self.row)?.as_integer()
}
#[must_use]
pub fn float(&self, item: &str) -> Option<f64> {
self.category.value(item, self.row)?.as_float()
}
#[must_use]
pub fn is_recorded(&self, item: &str) -> bool {
self.category
.value(item, self.row)
.is_some_and(CifValue::is_recorded)
}
}
#[cfg(test)]
#[path = "document_tests.rs"]
mod tests;