use super::row::AtomRow;
use crate::document::CifValueRef;
use crate::lexer::{Lexer, Quoting, Token};
use crate::lower::Field;
use molframe_core::execution::ExecutionContext;
use molframe_core::parallel::{BlockPlan, map_blocks_in};
const WINDOW_BYTES: usize = 4 * 1024 * 1024;
const CHUNK_BYTES: usize = 64 * 1024;
struct Chunk<'input> {
rows: Vec<AtomRow<'input>>,
terminator: Option<usize>,
whole: bool,
}
impl Chunk<'_> {
const fn bad() -> Self {
Self {
rows: Vec::new(),
terminator: None,
whole: false,
}
}
}
fn scan<'input>(text: &'input str, columns: &[Option<Field>]) -> Chunk<'input> {
let width = columns.len();
if width == 0 {
return Chunk::bad();
}
let mut lexer = Lexer::over(text);
let mut rows = Vec::with_capacity(text.len() / 48);
let mut row = AtomRow::new();
let mut cursor = 0;
let mut terminator = None;
loop {
let spanned = match lexer.next_token() {
Ok(Some(spanned)) => spanned,
Ok(None) => break,
Err(_) => return Chunk::bad(),
};
match spanned.token {
Token::Value(_, Quoting::Text) => return Chunk::bad(),
Token::Value(value, quoting) => {
if let Some(Some(field)) = columns.get(cursor) {
row.set(*field, CifValueRef::parse(value, quoting));
}
cursor += 1;
if cursor == width {
cursor = 0;
rows.push(std::mem::replace(&mut row, AtomRow::new()));
}
}
_ => {
let Ok(start) = usize::try_from(spanned.span.start.byte_offset) else {
return Chunk::bad();
};
terminator = Some(start);
break;
}
}
}
Chunk {
rows,
terminator,
whole: cursor == 0,
}
}
fn cuts(window: &str) -> Vec<usize> {
let bytes = window.as_bytes();
let mut cuts = vec![0];
let mut at: usize = 0;
while let Some(next) = at
.checked_add(CHUNK_BYTES)
.filter(|next| *next < bytes.len())
{
let Some(newline) = bytes
.get(next..)
.and_then(|tail| memchr::memchr(b'\n', tail))
else {
break;
};
at = next + newline + 1;
if at >= bytes.len() {
break;
}
cuts.push(at);
}
cuts.push(bytes.len());
cuts
}
pub(super) fn run<'input>(
text: &'input str,
columns: &[Option<Field>],
mut apply: impl FnMut(AtomRow<'input>),
) -> usize {
let context = ExecutionContext::default();
let mut done = 0;
while done < text.len() {
let mut end = done.saturating_add(WINDOW_BYTES).min(text.len());
if end < text.len() {
let Some(newline) = text
.as_bytes()
.get(end..)
.and_then(|tail| memchr::memchr(b'\n', tail))
else {
return done;
};
end += newline + 1;
}
let Some(window) = text.get(done..end) else {
return done;
};
let cuts = cuts(window);
let plan = BlockPlan::new(cuts.len() - 1, 1);
let Ok(chunks) = map_blocks_in(plan, &context, |index, _| {
let range = cuts.get(index).copied().zip(cuts.get(index + 1).copied());
let piece = range.and_then(|(from, to)| window.get(from..to));
match piece {
Some(piece) => scan(piece, columns),
None => Chunk::bad(),
}
}) else {
return done;
};
for (chunk, start) in chunks.into_iter().zip(cuts.iter().copied()) {
if !chunk.whole {
return done + start;
}
for row in chunk.rows {
apply(row);
}
if let Some(offset) = chunk.terminator {
return done + start + offset;
}
}
done = end;
}
done
}