readcon_core/stream.rs
1//! Frames parsed from a byte stream with about one frame in memory.
2
3use crate::error::ParseError;
4use crate::iterators::ConFrameIterator;
5use crate::types::ConFrame;
6use std::io::{self, Read};
7use std::path::Path;
8
9/// Error type of [`ConFrameReader`]: a [`ParseError`], or an input or
10/// output error from the source.
11pub type StreamError = Box<dyn std::error::Error + Send + Sync>;
12
13/// Bytes requested from the source when the buffer has no whole frame.
14pub const DEFAULT_READ_SIZE: usize = 1 << 20;
15
16/// Parses frames from any [`Read`], such as a gzip or Zstandard decoder.
17///
18/// A plain CON stream on standard input is the same kind of reader.
19/// Lock stdin and pass it here. The example uses a cursor so the
20/// doctest does not wait on a terminal.
21///
22/// ```
23/// use readcon_core::stream::ConFrameReader;
24/// use std::io::Cursor;
25/// let text = include_str!("../resources/test/tiny_cuh2.con");
26/// let count = ConFrameReader::new(Cursor::new(text.as_bytes()))
27/// .map(|frame| frame.expect("frame"))
28/// .count();
29/// assert!(count >= 1);
30/// ```
31///
32/// The buffer keeps the bytes not yet returned and room for one read.
33/// Before each read it moves those bytes to its front, and it grows only
34/// to fit them plus that read.
35/// Each read asks for `read_size` bytes.
36/// The request grows to an eighth of the unread bytes when that exceeds
37/// `read_size`, so a large frame needs few reads.
38/// The capacity therefore stays near the largest frame, with the lines
39/// the parser looks at after it, plus one read.
40/// For a frame longer than eight reads, the read term is up to an eighth
41/// of that frame.
42///
43/// Frames and errors are those of [`ConFrameIterator::from_bytes`] over
44/// the whole stream, up to and including the first error.
45/// When the source fails, the reader returns the frames that `from_bytes`
46/// returns over the bytes read before the failure, up to its first error,
47/// and then the source error.
48/// After an error the reader returns `None`.
49pub struct ConFrameReader<R> {
50 src: R,
51 /// Initialized storage, with the unread bytes in `buf[start..end]`.
52 buf: Vec<u8>,
53 start: usize,
54 end: usize,
55 read_size: usize,
56 eof: bool,
57 failed: Option<io::Error>,
58 done: bool,
59}
60
61impl ConFrameReader<Box<dyn Read + Send>> {
62 /// A reader over `path`, decompressed by
63 /// [`crate::compression::open_decoded`].
64 pub fn open(path: &Path) -> Result<Self, Box<dyn std::error::Error>> {
65 Ok(Self::new(crate::compression::open_decoded(path)?))
66 }
67}
68
69impl<R: Read> ConFrameReader<R> {
70 /// A reader that asks `src` for [`DEFAULT_READ_SIZE`] bytes at a time.
71 pub fn new(src: R) -> Self {
72 Self::with_read_size(src, DEFAULT_READ_SIZE)
73 }
74
75 /// Like [`Self::new`], asking the source for `read_size` bytes (at
76 /// least 1) whenever the buffer has no whole frame.
77 pub fn with_read_size(src: R, read_size: usize) -> Self {
78 Self {
79 src,
80 buf: Vec::new(),
81 start: 0,
82 end: 0,
83 read_size: read_size.max(1),
84 eof: false,
85 failed: None,
86 done: false,
87 }
88 }
89
90 /// Skip one frame without parsing its atoms, as
91 /// [`ConFrameIterator::forward`] does.
92 pub fn forward(&mut self) -> Option<Result<(), StreamError>> {
93 self.step(|it| it.forward().map(|r| r.map(|()| ((), it.byte_pos()))))
94 }
95
96 /// Run `op` on a fresh iterator over the buffered whole lines.
97 /// Its result is final once the stream has ended, or once the lines
98 /// after its cursor fix every look-ahead decision.
99 /// Otherwise read more.
100 ///
101 /// After a source error no more bytes arrive, so the buffer is parsed
102 /// as a whole input.
103 /// A frame from it is returned, and an error or the end of the buffer
104 /// gives way to the source error.
105 fn step<T>(
106 &mut self,
107 mut op: impl FnMut(&mut ConFrameIterator<'_>) -> Option<Result<(T, usize), ParseError>>,
108 ) -> Option<Result<T, StreamError>> {
109 let mut cut = false;
110 while !self.done {
111 let ended = self.eof || self.failed.is_some();
112 let rest = &self.buf[self.start..self.end];
113 let text = if ended {
114 rest
115 } else {
116 memchr::memrchr(b'\n', rest).map_or(&rest[..0], |i| &rest[..=i])
117 };
118 // Once a frame was cut by the buffer end, a skip walk checks
119 // each longer buffer first, so a frame larger than one read is
120 // not parsed in full once per read.
121 if ended || !cut || {
122 let mut probe = ConFrameIterator::from_bytes(text);
123 probe.forward();
124 settled(text, probe.byte_pos())
125 } {
126 let mut it = ConFrameIterator::from_bytes(text);
127 let result = op(&mut it);
128 if ended || settled(text, it.byte_pos()) {
129 match result {
130 Some(Ok((value, used))) => {
131 self.start += used;
132 return Some(Ok(value));
133 }
134 other if self.failed.is_none() => {
135 self.done = true;
136 return other.map(|r| r.map(|(value, _)| value).map_err(Into::into));
137 }
138 _ => {}
139 }
140 }
141 }
142 cut = true;
143 if let Some(e) = self.failed.take() {
144 self.done = true;
145 return Some(Err(e.into()));
146 }
147 self.fill();
148 }
149 None
150 }
151
152 /// Move the unread bytes to the front, and grow the buffer to fit them
153 /// plus one read.
154 /// Then read until that read is full, the source ends, or it fails.
155 /// The bytes read before a failure are kept.
156 fn fill(&mut self) {
157 self.buf.copy_within(self.start..self.end, 0);
158 self.end -= self.start;
159 self.start = 0;
160 let target = self.end + self.read_size.max(self.end / 8);
161 if target > self.buf.len() {
162 self.buf.reserve_exact(target - self.buf.len());
163 self.buf.resize(target, 0);
164 }
165 while self.end < target {
166 match self.src.read(&mut self.buf[self.end..target]) {
167 Ok(0) => {
168 self.eof = true;
169 return;
170 }
171 Ok(n) => self.end += n,
172 Err(e) if e.kind() == io::ErrorKind::Interrupted => {}
173 Err(e) => {
174 self.failed = Some(e);
175 return;
176 }
177 }
178 }
179 }
180}
181
182/// Whether `text` has, after `pos`, any whitespace-only lines, then a
183/// line with text and one more line.
184/// The parser reads no further past its cursor.
185/// A scan for trailing whitespace stops at the line with text.
186/// A section probe after a peeked blank reads two lines.
187/// It stops early when the first of them is whitespace.
188/// `text` ends in a newline, so every piece is a whole line.
189fn settled(text: &[u8], pos: usize) -> bool {
190 let mut lines = text[pos.min(text.len())..].split_inclusive(|&b| b == b'\n');
191 lines.any(|line| !line.iter().all(u8::is_ascii_whitespace)) && lines.next().is_some()
192}
193
194impl<R: Read> Iterator for ConFrameReader<R> {
195 type Item = Result<ConFrame, StreamError>;
196
197 fn next(&mut self) -> Option<Self::Item> {
198 self.step(|it| it.next_with_end())
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205
206 /// A zero-atom frame of `len` bytes, padded by its user line.
207 fn zero_atom_frame(len: usize) -> Vec<u8> {
208 let tail = "\n{\"con_spec_version\":2}\n10.000000 10.000000 10.000000\n\
209 90.000000 90.000000 90.000000\n\n\n0\n\n\n";
210 let mut frame = vec![b'U'; len - tail.len()];
211 frame.extend_from_slice(tail.as_bytes());
212 frame
213 }
214
215 /// The buffer after the first frame of `frame` then `after`.
216 fn after_first(frame: &[u8], after: &[u8]) -> (usize, usize) {
217 let text = [frame, after].concat();
218 let mut reader = ConFrameReader::new(&text[..]);
219 let want = ConFrameIterator::from_bytes(frame).next().unwrap().unwrap();
220 assert_eq!(reader.next().unwrap().unwrap(), want);
221 assert_eq!(reader.start, frame.len());
222 (reader.buf.len(), reader.buf.capacity())
223 }
224
225 #[test]
226 fn a_frame_over_one_read_grows_the_buffer_to_about_one_frame_and_one_read() {
227 let small = zero_atom_frame(100).repeat(2);
228 for len in [(1 << 20) + 57, (1 << 21) + 57] {
229 let frame = zero_atom_frame(len);
230 for after in [&small[..], &[][..]] {
231 let (filled, capacity) = after_first(&frame, after);
232 assert!(filled <= capacity);
233 assert!(
234 capacity <= len + DEFAULT_READ_SIZE,
235 "frame {len}, {} bytes after it: capacity {capacity}",
236 after.len()
237 );
238 }
239 }
240 }
241
242 #[test]
243 fn a_frame_of_many_reads_grows_the_buffer_by_an_eighth_at_most() {
244 let read_size = 4096;
245 let len = 64 * read_size + 57;
246 let text = [zero_atom_frame(len), zero_atom_frame(100)]
247 .concat()
248 .repeat(2);
249 let mut reader = ConFrameReader::with_read_size(&text[..], read_size);
250 for _ in 0..4 {
251 assert!(reader.next().unwrap().is_ok());
252 assert!(reader.buf.capacity() <= len + len / 8 + read_size);
253 }
254 assert!(reader.next().is_none());
255 }
256}