mycelium_trace/
buf.rs

1use alloc::{boxed::Box, string::String, vec::Vec};
2use core::{
3    fmt::{self, Write},
4    num::Wrapping,
5    ops::{Bound, RangeBounds},
6};
7
8/// A ring buffer of fixed-size lines.
9#[derive(Debug)]
10pub struct LineBuf {
11    lines: Box<[Line]>,
12    /// The maximum length of each line in the buffer
13    line_len: usize,
14    start: Wrapping<usize>,
15    end: Wrapping<usize>,
16}
17
18/// Configuration for a [`LineBuf`].
19#[derive(Copy, Clone, Debug)]
20#[non_exhaustive]
21pub struct BufConfig {
22    pub line_len: usize,
23    pub lines: usize,
24}
25
26#[derive(Copy, Clone, Debug)]
27#[must_use = "iterators do nothing if not iterated over"]
28pub struct Iter<'buf> {
29    buf: &'buf LineBuf,
30    idx: Wrapping<usize>,
31    end: Wrapping<usize>,
32}
33
34struct Line {
35    line: String,
36    stamp: Wrapping<usize>,
37}
38
39#[cfg(test)]
40macro_rules! test_dbg {
41    ($x:expr) => {
42        dbg!($x)
43    };
44}
45
46#[cfg(not(test))]
47macro_rules! test_dbg {
48    ($x:expr) => {
49        $x
50    };
51}
52
53impl LineBuf {
54    #[must_use]
55    pub fn new(config: BufConfig) -> Self {
56        let line_len = config.line_len;
57        Self {
58            lines: (0..config.lines)
59                .map(|stamp| Line {
60                    stamp: Wrapping(stamp),
61                    line: String::with_capacity(line_len),
62                })
63                .collect::<Vec<_>>()
64                .into(),
65            line_len,
66            start: Wrapping(0),
67            end: Wrapping(0),
68        }
69    }
70
71    pub fn iter(&self) -> Iter<'_> {
72        Iter {
73            idx: self.start,
74            end: self.end,
75            buf: self,
76        }
77    }
78
79    pub fn lines(&self, range: impl RangeBounds<usize>) -> Iter<'_> {
80        let idx = match range.start_bound() {
81            Bound::Excluded(&offset) => self.wrap_offset(offset + 1),
82            Bound::Included(&offset) => self.wrap_offset(offset),
83            Bound::Unbounded => self.start,
84        };
85        let end = match range.end_bound() {
86            Bound::Excluded(&offset) => self.wrap_offset(offset),
87            Bound::Included(&offset) => self.wrap_offset(offset + 1),
88            Bound::Unbounded => self.end,
89        };
90        Iter {
91            idx,
92            end,
93            buf: self,
94        }
95    }
96
97    fn wrap_offset(&self, offset: usize) -> Wrapping<usize> {
98        self.start + Wrapping(offset)
99    }
100
101    fn advance(&mut self) {
102        self.end += 1;
103    }
104
105    fn wrap_idx(&self, Wrapping(idx): Wrapping<usize>) -> usize {
106        idx % self.lines.len()
107    }
108
109    fn line_mut(&mut self) -> &mut String {
110        let idx = self.wrap_idx(self.end);
111        let Line { stamp, line } = &mut self.lines[idx];
112        if *stamp != self.end {
113            *stamp = self.end;
114            line.clear();
115
116            if idx == self.start.0 {
117                // we are writing to the current start index, so scootch the
118                // start forward by one.
119                self.start += 1;
120            }
121        }
122        line
123    }
124
125    fn write_chunk<'s>(&mut self, s: &'s str) -> Option<&'s str> {
126        let rem = self.line_len - self.line_mut().len();
127        let (line, next) = if s.len() > rem {
128            let (this, next) = s.split_at(rem);
129            (this, Some(next))
130        } else {
131            (s, None)
132        };
133        self.line_mut().push_str(line);
134        next
135    }
136
137    fn write_line(&mut self, mut line: &str) {
138        while let Some(next) = test_dbg!(self.write_chunk(test_dbg!(line))) {
139            line = next;
140            test_dbg!(self.advance());
141        }
142    }
143}
144
145impl Write for &mut LineBuf {
146    fn write_str(&mut self, mut s: &str) -> fmt::Result {
147        let ends_with_newline = if let Some(stripped) = s.strip_suffix('\n') {
148            s = stripped;
149            true
150        } else {
151            false
152        };
153
154        let mut lines = s.split('\n');
155        if let Some(line) = lines.next() {
156            self.write_line(line);
157            for line in lines {
158                self.advance();
159                self.write_line(line);
160            }
161        }
162
163        if ends_with_newline {
164            self.advance();
165        }
166
167        Ok(())
168    }
169}
170
171impl<'buf> Iterator for Iter<'buf> {
172    type Item = &'buf str;
173
174    fn next(&mut self) -> Option<Self::Item> {
175        let idx = self.idx;
176        if idx >= self.end {
177            return None;
178        }
179        self.idx += 1;
180        let Line { line, stamp } = self.buf.lines.get(self.buf.wrap_idx(idx))?;
181        if *stamp != idx {
182            return None;
183        }
184        Some(line.as_str())
185    }
186}
187
188impl fmt::Debug for Line {
189    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
190        let Line { line, stamp } = self;
191        write!(f, "{line:?}:{stamp}")
192    }
193}
194
195// === impl BufConfig ===
196
197impl Default for BufConfig {
198    fn default() -> Self {
199        Self {
200            line_len: 80,
201            // CHOSEN BY FAIR DIE ROLL, GUARANTEED TO BE RANDOM
202            lines: 120,
203        }
204    }
205}
206
207#[cfg(test)]
208mod tests {
209    use core::slice::SliceIndex;
210
211    use super::*;
212
213    #[test]
214    fn basic() {
215        let mut buf = LineBuf::new(BufConfig {
216            line_len: 6,
217            lines: 6,
218        });
219        writeln!(&mut buf, "hello").unwrap();
220        writeln!(&mut buf, "world").unwrap();
221        writeln!(&mut buf, "have\nlots").unwrap();
222        writeln!(&mut buf, "of").unwrap();
223        writeln!(&mut buf, "fun").unwrap();
224
225        let mut iter = buf.iter();
226        assert_eq!(
227            test_dbg!(iter.next()),
228            Some("hello"),
229            "\n   buf: {buf:?}\n  iter: {iter:?}"
230        );
231        assert_eq!(
232            test_dbg!(iter.next()),
233            Some("world"),
234            "\n   buf: {buf:?}\n  iter: {iter:?}"
235        );
236        assert_eq!(
237            test_dbg!(iter.next()),
238            Some("have"),
239            "\n   buf: {buf:?}\n  iter: {iter:?}"
240        );
241        assert_eq!(
242            test_dbg!(iter.next()),
243            Some("lots"),
244            "\n   buf: {buf:?}\n  iter: {iter:?}"
245        );
246        assert_eq!(
247            test_dbg!(iter.next()),
248            Some("of"),
249            "\n   buf: {buf:?}\n  iter: {iter:?}"
250        );
251        assert_eq!(
252            test_dbg!(iter.next()),
253            Some("fun"),
254            "\n   buf: {buf:?}\n  iter: {iter:?}"
255        );
256        assert_eq!(test_dbg!(iter.next()), None);
257    }
258
259    #[test]
260    fn buffer_wraparound() {
261        let mut buf = LineBuf::new(BufConfig {
262            line_len: 7,
263            lines: 6,
264        });
265        writeln!(&mut buf, "hello").unwrap();
266        writeln!(&mut buf, "world").unwrap();
267        writeln!(&mut buf, "have\nlots").unwrap();
268        writeln!(&mut buf, "of").unwrap();
269        writeln!(&mut buf, "fun").unwrap();
270        writeln!(&mut buf, "goodbye").unwrap();
271
272        assert_slicelike(
273            "buffer wraparound",
274            &buf,
275            &["world", "have", "lots", "of", "fun", "goodbye"],
276            ..,
277        )
278    }
279
280    #[test]
281    fn line_wrapping() {
282        let mut buf = LineBuf::new(BufConfig {
283            line_len: 4,
284            lines: 6,
285        });
286        writeln!(&mut buf, "this is a very long line").unwrap();
287
288        dbg!(&buf);
289        let mut iter = buf.iter();
290        assert_eq!(
291            test_dbg!(iter.next()),
292            Some("this"),
293            "\n   buf: {buf:?}\n  iter: {iter:?}"
294        );
295        assert_eq!(
296            test_dbg!(iter.next()),
297            Some(" is "),
298            "\n   buf: {buf:?}\n  iter: {iter:?}"
299        );
300        assert_eq!(
301            test_dbg!(iter.next()),
302            Some("a ve"),
303            "\n   buf: {buf:?}\n  iter: {iter:?}"
304        );
305        assert_eq!(
306            test_dbg!(iter.next()),
307            Some("ry l"),
308            "\n   buf: {buf:?}\n  iter: {iter:?}"
309        );
310        assert_eq!(
311            test_dbg!(iter.next()),
312            Some("ong "),
313            "\n   buf: {buf:?}\n  iter: {iter:?}"
314        );
315        assert_eq!(
316            test_dbg!(iter.next()),
317            Some("line"),
318            "\n   buf: {buf:?}\n  iter: {iter:?}"
319        );
320        assert_eq!(
321            test_dbg!(iter.next()),
322            None,
323            "\n   buf: {buf:?}\n  iter: {iter:?}"
324        );
325    }
326
327    #[test]
328    fn range_iter_unbounded() {
329        let mut buf = LineBuf::new(BufConfig {
330            line_len: 6,
331            lines: 6,
332        });
333        let expected = ["hello", "world", "have", "lots", "of", "fun"];
334        fill(&mut buf, &expected);
335        assert_slicelike("unbounded", &buf, &expected, ..)
336    }
337
338    #[test]
339    fn range_iter_basic() {
340        let mut buf = LineBuf::new(BufConfig {
341            line_len: 6,
342            lines: 6,
343        });
344
345        let expected = ["hello", "world", "have", "lots", "of", "fun"];
346        fill(&mut buf, &expected);
347        test_range_iters(&buf, &expected)
348    }
349
350    #[test]
351    fn range_iter_buf_wrapped() {
352        let mut buf = LineBuf::new(BufConfig {
353            line_len: 7,
354            lines: 6,
355        });
356        writeln!(&mut buf, "hello").unwrap();
357        writeln!(&mut buf, "world").unwrap();
358        writeln!(&mut buf, "have\nlots").unwrap();
359        writeln!(&mut buf, "of").unwrap();
360        writeln!(&mut buf, "fun").unwrap();
361        writeln!(&mut buf, "goodbye").unwrap();
362
363        let expected = ["world", "have", "lots", "of", "fun", "goodbye"];
364        test_range_iters(&buf, &expected);
365    }
366
367    fn test_range_iters(buf: &LineBuf, expected: &[&str]) {
368        assert_slicelike("unbounded", buf, expected, ..);
369
370        assert_slicelike("start inclusive", buf, expected, 2..);
371        assert_slicelike("start inclusive", buf, expected, 3..);
372        assert_slicelike("start inclusive", buf, expected, 4..);
373        assert_slicelike("start inclusive", buf, expected, 5..);
374
375        assert_slicelike("end inclusive", buf, expected, ..=2);
376        assert_slicelike("end inclusive", buf, expected, ..=5);
377
378        assert_slicelike("end exclusive", buf, expected, ..2);
379        assert_slicelike("end exclusive", buf, expected, ..5);
380        assert_slicelike("end exclusive", buf, expected, ..6)
381    }
382
383    fn fill(mut buf: &mut LineBuf, strs: &[&str]) {
384        for item in strs {
385            writeln!(buf, "{item}").unwrap();
386        }
387    }
388
389    fn assert_slicelike<'ex>(
390        kind: &str,
391        buf: &LineBuf,
392        expected: &'ex [&'ex str],
393        range: impl RangeBounds<usize>
394            + SliceIndex<[&'ex str], Output = [&'ex str]>
395            + Clone
396            + fmt::Debug,
397    ) {
398        let slice = &expected[range.clone()];
399        let mut iter = buf.lines(range.clone());
400        for &expected in slice {
401            assert_eq!(
402                Some(expected),
403                iter.next(),
404                "\n range: {range:?}\n   buf: {buf:?}\n  iter: {iter:?}\n   exp: {slice:?}\n  kind: {kind}"
405            )
406        }
407        assert_eq!(
408            None,
409            iter.next(),
410            "\n range: {range:?}\n   buf: {buf:?}\n  iter: {iter:?}\n   exp: {slice:?}\n  kind: {kind}"
411        )
412    }
413}