praxis_llm/buffer_utils/
buffering.rs

1use anyhow::Result;
2use std::collections::VecDeque;
3
4/// Circular buffer for efficient line-based parsing
5/// Uses VecDeque for zero-copy line extraction
6pub struct CircularLineBuffer {
7    buffer: VecDeque<u8>,
8}
9
10impl CircularLineBuffer {
11    /// Create a new buffer with specified capacity
12    pub fn with_capacity(capacity: usize) -> Self {
13        Self {
14            buffer: VecDeque::with_capacity(capacity),
15        }
16    }
17
18    /// Add bytes to the buffer
19    pub fn extend(&mut self, bytes: &[u8]) {
20        self.buffer.extend(bytes);
21    }
22
23    /// Extract next line (up to \n) from buffer
24    /// Returns None if no complete line is available
25    pub fn next_line(&mut self) -> Option<Result<String>> {
26        // Find newline position
27        let newline_pos = self.buffer.iter().position(|&b| b == b'\n')?;
28
29        // Drain bytes up to and including newline (zero-copy!)
30        let line_bytes: Vec<u8> = self.buffer.drain(..=newline_pos).collect();
31
32        // Convert to UTF-8 string
33        match std::str::from_utf8(&line_bytes) {
34            Ok(line_str) => Some(Ok(line_str.trim().to_string())),
35            Err(e) => Some(Err(anyhow::anyhow!("Invalid UTF-8: {}", e))),
36        }
37    }
38
39    /// Current buffer size
40    pub fn len(&self) -> usize {
41        self.buffer.len()
42    }
43
44    /// Check if buffer is empty
45    pub fn is_empty(&self) -> bool {
46        self.buffer.is_empty()
47    }
48}
49
50#[cfg(test)]
51mod tests {
52    use super::*;
53
54    #[test]
55    fn test_circular_buffer_basic() {
56        let mut buffer = CircularLineBuffer::with_capacity(64);
57        
58        buffer.extend(b"line1\nline2\n");
59        
60        assert_eq!(buffer.next_line().unwrap().unwrap(), "line1");
61        assert_eq!(buffer.next_line().unwrap().unwrap(), "line2");
62        assert!(buffer.next_line().is_none());
63    }
64
65    #[test]
66    fn test_partial_line() {
67        let mut buffer = CircularLineBuffer::with_capacity(64);
68        
69        buffer.extend(b"partial");
70        assert!(buffer.next_line().is_none());
71        
72        buffer.extend(b" line\n");
73        assert_eq!(buffer.next_line().unwrap().unwrap(), "partial line");
74    }
75}
76