Skip to main content

journald_export_parser_rs/
shiftbuffer.rs

1//! Operate on the tail of a data stream that is managed using a buffer of
2//! limited size.
3//!
4//! A [ShiftBuffer] enables pointer arithmetics on data that is occasionally
5//! 'shifted'; i.e. the data is being moved to the beginning of the buffer to
6//! retain its size while allowing for further data to be read into the buffer.
7//! To that end, [ShiftBuffer] maintains a sliding window which can be extended
8//! (the upper end moves up) or shrunk (the lower end moves up). To access the
9//! data within the window, the buffer can be indexed using a [Pointer].
10//!
11//! Typically, this is used in a scenario where one wants to operate on the tail
12//! of a continuous data stream while only allocating a fixed buffer. Whenever
13//! the buffer is 'shifted', it is conceptually moved forward in the data
14//! stream.
15//!
16//! The following is an illustration of the state before and after a shift.
17//! Here, the cursor is a pointer into the window. Technically, a pointer
18//! retains its position within the original data stream. We call this position
19//! 'absolute' and it can be revealed with [Pointer::abs].
20//!
21//! ```text
22//! before:
23//!         |<----------- buffer ----------->|
24//!         |     |<----- window ----->|
25//!                           ^
26//!                           |
27//! ~~ data stream ~~~~~~~~[cursor]~~~~~
28//! after:                    |
29//!                           v
30//!               |<----------- buffer ----------->|
31//!               |<----- window ----->|<- free -->|
32//! ```
33//!
34//! Following the illustration above, this is the state after the window has
35//! been extended:
36//!
37//! ```text
38//! ~~ data stream ~~~~~~~~[cursor]~~~~~~~~~~
39//!                           |
40//!                           v
41//!               |<----------- buffer ----------->|
42//!               |<------- window --------->|< f >|
43//! ```
44//!
45//! In a typical scenario, one would call [ShiftBuffer::make_room] whenever more
46//! data needs to be read into the buffer. This method either shifts the window
47//! or doubles the buffer size, depending on whether the window currently covers
48//! the entire buffer or not.
49
50use std::ops::{Add, AddAssign, Index, IndexMut, Range, Sub, SubAssign};
51
52#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy, Default)]
53pub struct Pointer(usize);
54
55impl Pointer {
56    /// The pointer returns the _absolute_ position in the byte stream that was
57    /// consumed using the shift buffer.
58    pub fn abs(&self) -> usize {
59        self.0
60    }
61}
62
63impl Add<usize> for Pointer {
64    type Output = Pointer;
65
66    fn add(self, rhs: usize) -> Self::Output {
67        Self(self.0 + rhs)
68    }
69}
70
71impl AddAssign<usize> for Pointer {
72    fn add_assign(&mut self, rhs: usize) {
73        self.0 += rhs
74    }
75}
76
77impl Sub<usize> for Pointer {
78    type Output = Pointer;
79
80    fn sub(self, rhs: usize) -> Self::Output {
81        Self(self.0 - rhs)
82    }
83}
84
85impl SubAssign<usize> for Pointer {
86    fn sub_assign(&mut self, rhs: usize) {
87        self.0 -= rhs
88    }
89}
90
91impl Sub<Pointer> for Pointer {
92    type Output = usize;
93
94    fn sub(self, rhs: Pointer) -> Self::Output {
95        self.0 - rhs.0
96    }
97}
98
99pub struct ShiftBuffer<T> {
100    buf: Vec<T>,
101    // The absolute position of the lower end of the window in the overall byte
102    // stream. To put it differently, this is the total sum of all advances.
103    offset: Pointer,
104    lower: Pointer,
105    upper: Pointer,
106}
107
108impl<T: Default + Copy> ShiftBuffer<T> {
109    pub fn new(init_size: usize) -> Self {
110        let buf = (0..init_size).map(|_| T::default()).collect();
111        Self {
112            buf,
113            offset: Pointer::default(),
114            lower: Pointer::default(),
115            upper: Pointer::default(),
116        }
117    }
118
119    /// Moves the lower end of the window by `n`.
120    pub fn shrink(&mut self, n: usize) -> Pointer {
121        assert!(self.lower + n < self.upper);
122        self.lower += n;
123        self.lower
124    }
125
126    /// Moves the upper end of the window by `n`.
127    pub fn extend(&mut self, n: usize) -> Pointer {
128        assert!(self.relative_pos(self.upper) + n <= self.buf.len());
129        self.upper += n;
130        self.upper
131    }
132
133    /// Make room in the buffer for more data.
134    ///
135    /// If the upper end of the window is not at the stop position of the
136    /// internal buffer, this method has no effect on the state of the buffer.
137    ///
138    /// Otherwise, it performs either of two operations: if the lower end is at
139    /// the beginning of the buffer (the window covers the entire buffer), the
140    /// buffer is extended. Otherwise, the buffer is shifted; i.e., all entries
141    /// prior to the lower end are discarded and the content is moved to the
142    /// beginning of the buffer.
143    ///
144    /// In all cases, the return value of this method is the same as for
145    /// [ShiftBuffer::free].
146    pub fn make_room(&mut self) -> &mut [T] {
147        if self.relative_pos(self.upper) == self.buf.len() {
148            if self.lower == self.offset {
149                self.buf.extend((0..self.buf.len()).map(|_| T::default()))
150            } else {
151                self.shift();
152            }
153        }
154        self.free()
155    }
156
157    pub fn shift(&mut self) {
158        let d = self.upper.abs() - self.lower.abs();
159        for p in 0..d {
160            self.buf[p] = self.buf[p + d]
161        }
162        self.offset = self.lower;
163    }
164
165    pub fn free(&mut self) -> &mut [T] {
166        let r = self.relative_pos(self.upper);
167        &mut self.buf[r..]
168    }
169
170    pub fn lower(&self) -> Pointer {
171        self.lower
172    }
173
174    pub fn upper(&self) -> Pointer {
175        self.upper
176    }
177
178    pub fn relative_pos(&self, p: Pointer) -> usize {
179        debug_assert!(self.lower <= p && p <= self.upper);
180        p - self.offset
181    }
182
183    /// Create a shift buffer that contains a copy of the current window.
184    pub fn clone_window(&self) -> ShiftBuffer<T> {
185        let (l, u) = (self.lower, self.upper);
186        ShiftBuffer {
187            buf: self[l..u].to_vec(),
188            offset: l,
189            lower: l,
190            upper: u,
191        }
192    }
193}
194
195impl<T: Default + Copy> Index<Pointer> for ShiftBuffer<T> {
196    type Output = T;
197
198    fn index(&self, index: Pointer) -> &Self::Output {
199        debug_assert!(self.lower <= index && index <= self.upper);
200        &self.buf[self.relative_pos(index)]
201    }
202}
203
204impl<T: Default + Copy> IndexMut<Pointer> for ShiftBuffer<T> {
205    fn index_mut(&mut self, index: Pointer) -> &mut Self::Output {
206        debug_assert!(self.lower <= index && index <= self.upper);
207        let r = self.relative_pos(index);
208        &mut self.buf[r]
209    }
210}
211
212impl<T: Default + Copy> Index<Range<Pointer>> for ShiftBuffer<T> {
213    type Output = [T];
214
215    fn index(&self, r: Range<Pointer>) -> &Self::Output {
216        debug_assert!(r.start <= r.end);
217        debug_assert!(self.lower <= r.start && r.start <= self.upper);
218        debug_assert!(self.lower <= r.end && r.end <= self.upper);
219        &self.buf[self.relative_pos(r.start)..self.relative_pos(r.end)]
220    }
221}
222
223#[cfg(test)]
224mod tests {
225    use super::ShiftBuffer;
226
227    #[test]
228    fn store_simple_string() {
229        let input_string = "ABC";
230        let mut sbuf = ShiftBuffer::<u8>::new(1 << 10);
231        let (lower, upper) = (sbuf.lower(), sbuf.extend(3));
232
233        let mut cursor = lower;
234        for b in input_string.as_bytes() {
235            sbuf[cursor] = *b;
236            cursor += 1;
237        }
238
239        assert_eq!(&sbuf[lower..upper], input_string.as_bytes());
240    }
241}