use super::extra::ProjectedSink;
use super::projection;
use super::stream::DirectSink;
use crate::document::Document;
use crate::lower::finish_streamed;
use crate::parser::{CifEventSink, parse_into};
use molframe_core::diagnostic::{Code, Diagnostic, Diagnostics};
use molframe_core::io::{InputBuffer, ReadOptions, ReadResult};
use molframe_core::structure::Structure;
type ProjectionResult<T> = Result<(Document, Structure, T, Vec<Diagnostic>), Vec<Diagnostic>>;
pub(in crate::reader) fn read(input: &InputBuffer, options: &ReadOptions) -> ReadResult {
read_with_metadata(input, options, |_| false)
.map(|(_, structure, findings)| (structure, findings))
}
pub(in crate::reader) fn read_with_metadata(
input: &InputBuffer,
options: &ReadOptions,
keep_category: fn(&str) -> bool,
) -> Result<(Document, Structure, Vec<Diagnostic>), Vec<Diagnostic>> {
let (direct, findings) =
parse_into(input, DirectSink::new(options, keep_category, input.len()))?;
finish_direct(direct, findings, options)
}
pub(in crate::reader) fn read_with_projection<S>(
input: &InputBuffer,
options: &ReadOptions,
keep_category: fn(&str) -> bool,
projection: S,
) -> ProjectionResult<S::Output>
where
S: CifEventSink,
{
let sink = ProjectedSink::new(options, keep_category, input.len(), projection);
let ((direct, projected), findings) = parse_into(input, sink)?;
let (document, structure, findings) = finish_direct(direct, findings, options)?;
Ok((document, structure, projected, findings))
}
fn finish_direct(
direct: super::stream::DirectOutput,
findings: Vec<Diagnostic>,
options: &ReadOptions,
) -> Result<(Document, Structure, Vec<Diagnostic>), Vec<Diagnostic>> {
let projection = direct.projection;
if !projection.layout.has_atom_site {
return Err(vec![
Diagnostic::new(Code::E2001)
.with_message("the block contains no coordinates")
.in_category("atom_site"),
]);
}
if !projection.layout.supports_direct() {
return Err(unsupported_layout(&projection.layout, findings));
}
if !direct.setup_errors.is_empty() {
return Err(direct.setup_errors);
}
let Some(models) = direct.models else {
return Err(vec![
Diagnostic::new(Code::E2001)
.with_message("the block contains no coordinates")
.in_category("atom_site"),
]);
};
let (structure, findings) = finish_streamed(&projection.metadata, options, findings, models)?;
Ok((projection.metadata, structure, findings))
}
fn unsupported_layout(
layout: &projection::AtomLayout,
findings: Vec<Diagnostic>,
) -> Vec<Diagnostic> {
if findings.iter().any(|finding| finding.code() == Code::E1103) {
return findings;
}
let message = if layout.model_count() == 0 {
"atom_site does not contain complete coordinate rows"
} else {
"atom_site is split across scalar, repeated, or duplicate loop items"
};
let mut diagnostics = Diagnostics::with_capacity(findings.len() + 1);
diagnostics.extend(findings);
diagnostics.push(
Diagnostic::new(Code::E1104)
.with_message(message)
.in_category("atom_site"),
);
diagnostics.finish()
}
pub(in crate::reader) fn keep_lowering_category(category: &str) -> bool {
matches!(
category,
"entry"
| "struct"
| "refine"
| "exptl"
| "cell"
| "entity"
| "entity_poly"
| "entity_poly_seq"
| "struct_asym"
| "struct_conn"
| "struct_conf"
| "struct_sheet_range"
| "struct_ref"
| "struct_ref_seq"
| "atom_site_anisotrop"
)
}