Skip to main content

molframe_pdb/reader/
entry.rs

1//! The entry point: bytes in, structure and findings out.
2
3use super::ensemble::{CommonRecords, read_ragged};
4use super::lines::{Line, Lines};
5use super::numbers::decode_window;
6use super::state::ReadState;
7use molframe_core::diagnostic::{Code, Diagnostic};
8use molframe_core::execution::ExecutionContext;
9use molframe_core::io::{Format, InputBuffer, ReadOptions, ReadResult, Reader};
10
11/// The reader for the legacy fixed-column format.
12#[derive(Clone, Copy, Debug, Default)]
13pub struct PdbReader;
14
15impl Reader for PdbReader {
16    const FORMAT: Format = Format::Pdb;
17
18    fn read(input: &InputBuffer, options: &ReadOptions) -> ReadResult {
19        read_as(input, options, Format::Pdb)
20    }
21}
22
23/// Reads a structure from fixed-column text.
24///
25/// # Errors
26///
27/// Returns the findings that stopped the read: text that is not valid UTF-8, or
28/// a file with no coordinate records in it at all.
29pub fn read(input: &InputBuffer, options: &ReadOptions) -> ReadResult {
30    read_as(input, options, Format::Pdb)
31}
32
33/// Reads PQR coordinates with partial charge and radius annotations.
34///
35/// # Errors
36///
37/// Returns findings when text, coordinate fields or variant values cannot be
38/// interpreted under the requested parse mode.
39pub fn read_pqr(input: &InputBuffer, options: &ReadOptions) -> ReadResult {
40    read_as(input, options, Format::Pqr)
41}
42
43/// Reads PDBQT coordinates with partial charge and `AutoDock` type annotations.
44///
45/// # Errors
46///
47/// Returns findings when text, coordinate fields or variant values cannot be
48/// interpreted under the requested parse mode.
49pub fn read_pdbqt(input: &InputBuffer, options: &ReadOptions) -> ReadResult {
50    read_as(input, options, Format::Pdbqt)
51}
52
53/// Lines decoded ahead of the sequential walk, which bounds the extra memory.
54const WINDOW_LINES: usize = 1 << 15;
55
56fn read_as(input: &InputBuffer, options: &ReadOptions, variant: Format) -> ReadResult {
57    let Ok(text) = str::from_utf8(input.as_bytes()) else {
58        return Err(vec![
59            Diagnostic::new(Code::E1201).with_message("input is not valid text"),
60        ]);
61    };
62
63    let mut common = CommonRecords::new();
64    let mut state = ReadState::new(options, variant);
65    let context = ExecutionContext::default();
66    let mut lines = Lines::new(text);
67    let mut window = Vec::with_capacity(WINDOW_LINES);
68    loop {
69        window.clear();
70        let mut failure = None;
71        for line in lines.by_ref().take(WINDOW_LINES) {
72            match line {
73                Ok(line) => window.push(line),
74                Err(finding) => {
75                    failure = Some(finding);
76                    break;
77                }
78            }
79        }
80        let decoded = decode_window(&window, &context);
81        for (index, line) in window.iter().enumerate() {
82            common.observe(line);
83            let line = Line {
84                numbers: decoded.get(index).and_then(Option::as_ref),
85                ..*line
86            };
87            state.line(&line);
88        }
89        if let Some(finding) = failure {
90            return Err(vec![finding]);
91        }
92        if window.len() < WINDOW_LINES {
93            break;
94        }
95    }
96    if state.requires_ragged() {
97        drop(state);
98        return read_ragged(text, options, variant, &common);
99    }
100    state.finish()
101}
102
103#[cfg(test)]
104#[path = "entry_tests.rs"]
105mod tests;