Coverage Report

Created: 2025-02-21 07:11

/rust/registry/src/index.crates.io-6f17d22bba15001f/markup5ever-0.12.1/util/buffer_queue.rs
Line
Count
Source (jump to first uncovered line)
1
// Copyright 2014-2017 The html5ever Project Developers. See the
2
// COPYRIGHT file at the top-level directory of this distribution.
3
//
4
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7
// option. This file may not be copied, modified, or distributed
8
// except according to those terms.
9
10
//! The `BufferQueue` struct and helper types.
11
//!
12
//! This type is designed for the efficient parsing of string data, especially where many
13
//! significant characters are from the ascii range 0-63. This includes, for example, important
14
//! characters in xml/html parsing.
15
//!
16
//! Good and predictable performance is achieved by avoiding allocation where possible (a.k.a. zero
17
//! copy).
18
//!
19
//! [`BufferQueue`]: struct.BufferQueue.html
20
21
use std::collections::VecDeque;
22
23
use tendril::StrTendril;
24
25
pub use self::SetResult::{FromSet, NotFromSet};
26
use crate::util::smallcharset::SmallCharSet;
27
28
/// Result from [`pop_except_from`] containing either a character from a [`SmallCharSet`], or a
29
/// string buffer of characters not from the set.
30
///
31
/// [`pop_except_from`]: struct.BufferQueue.html#method.pop_except_from
32
/// [`SmallCharSet`]: ../struct.SmallCharSet.html
33
#[derive(PartialEq, Eq, Debug)]
34
pub enum SetResult {
35
    /// A character from the `SmallCharSet`.
36
    FromSet(char),
37
    /// A string buffer containing no characters from the `SmallCharSet`.
38
    NotFromSet(StrTendril),
39
}
40
41
/// A queue of owned string buffers, which supports incrementally consuming characters.
42
///
43
/// Internally it uses [`VecDeque`] and has the same complexity properties.
44
///
45
/// [`VecDeque`]: https://doc.rust-lang.org/std/collections/struct.VecDeque.html
46
#[derive(Debug)]
47
pub struct BufferQueue {
48
    /// Buffers to process.
49
    buffers: VecDeque<StrTendril>,
50
}
51
52
impl Default for BufferQueue {
53
    /// Create an empty BufferQueue.
54
    #[inline]
55
0
    fn default() -> Self {
56
0
        Self {
57
0
            buffers: VecDeque::with_capacity(16),
58
0
        }
59
0
    }
Unexecuted instantiation: <markup5ever::util::buffer_queue::BufferQueue as core::default::Default>::default
Unexecuted instantiation: <markup5ever::util::buffer_queue::BufferQueue as core::default::Default>::default
60
}
61
62
impl BufferQueue {
63
    /// Returns whether the queue is empty.
64
    #[inline]
65
0
    pub fn is_empty(&self) -> bool {
66
0
        self.buffers.is_empty()
67
0
    }
Unexecuted instantiation: <markup5ever::util::buffer_queue::BufferQueue>::is_empty
Unexecuted instantiation: <markup5ever::util::buffer_queue::BufferQueue>::is_empty
68
69
    /// Get the buffer at the beginning of the queue.
70
    #[inline]
71
0
    pub fn pop_front(&mut self) -> Option<StrTendril> {
72
0
        self.buffers.pop_front()
73
0
    }
74
75
    /// Add a buffer to the beginning of the queue.
76
    ///
77
    /// If the buffer is empty, it will be skipped.
78
0
    pub fn push_front(&mut self, buf: StrTendril) {
79
0
        if buf.len32() == 0 {
80
0
            return;
81
0
        }
82
0
        self.buffers.push_front(buf);
83
0
    }
84
85
    /// Add a buffer to the end of the queue.
86
    ///
87
    /// If the buffer is empty, it will be skipped.
88
0
    pub fn push_back(&mut self, buf: StrTendril) {
89
0
        if buf.len32() == 0 {
90
0
            return;
91
0
        }
92
0
        self.buffers.push_back(buf);
93
0
    }
94
95
    /// Look at the next available character without removing it, if the queue is not empty.
96
0
    pub fn peek(&self) -> Option<char> {
97
0
        debug_assert!(
98
0
            !self.buffers.iter().any(|el| el.len32() == 0),
99
0
            "invariant \"all buffers in the queue are non-empty\" failed"
100
        );
101
0
        self.buffers.front().map(|b| b.chars().next().unwrap())
102
0
    }
103
104
    /// Pops and returns either a single character from the given set, or
105
    /// a buffer of characters none of which are in the set.
106
    ///
107
    /// # Examples
108
    ///
109
    /// ```
110
    /// # #[macro_use] extern crate markup5ever;
111
    /// # #[macro_use] extern crate tendril;
112
    /// # fn main() {
113
    /// use markup5ever::buffer_queue::{BufferQueue, SetResult};
114
    ///
115
    /// let mut queue = BufferQueue::default();
116
    /// queue.push_back(format_tendril!(r#"<some_tag attr="text">SomeText</some_tag>"#));
117
    /// let set = small_char_set!(b'<' b'>' b' ' b'=' b'"' b'/');
118
    /// let tag = format_tendril!("some_tag");
119
    /// let attr = format_tendril!("attr");
120
    /// let attr_val = format_tendril!("text");
121
    /// assert_eq!(queue.pop_except_from(set), Some(SetResult::FromSet('<')));
122
    /// assert_eq!(queue.pop_except_from(set), Some(SetResult::NotFromSet(tag)));
123
    /// assert_eq!(queue.pop_except_from(set), Some(SetResult::FromSet(' ')));
124
    /// assert_eq!(queue.pop_except_from(set), Some(SetResult::NotFromSet(attr)));
125
    /// assert_eq!(queue.pop_except_from(set), Some(SetResult::FromSet('=')));
126
    /// assert_eq!(queue.pop_except_from(set), Some(SetResult::FromSet('"')));
127
    /// assert_eq!(queue.pop_except_from(set), Some(SetResult::NotFromSet(attr_val)));
128
    /// // ...
129
    /// # }
130
    /// ```
131
0
    pub fn pop_except_from(&mut self, set: SmallCharSet) -> Option<SetResult> {
132
0
        let (result, now_empty) = match self.buffers.front_mut() {
133
0
            None => (None, false),
134
0
            Some(buf) => {
135
0
                let n = set.nonmember_prefix_len(buf);
136
0
                if n > 0 {
137
                    let out;
138
0
                    unsafe {
139
0
                        out = buf.unsafe_subtendril(0, n);
140
0
                        buf.unsafe_pop_front(n);
141
0
                    }
142
0
                    (Some(NotFromSet(out)), buf.is_empty())
143
                } else {
144
0
                    let c = buf.pop_front_char().expect("empty buffer in queue");
145
0
                    (Some(FromSet(c)), buf.is_empty())
146
                }
147
            },
148
        };
149
150
        // Unborrow self for this part.
151
0
        if now_empty {
152
0
            self.buffers.pop_front();
153
0
        }
154
155
0
        result
156
0
    }
157
158
    /// Consume bytes matching the pattern, using a custom comparison function `eq`.
159
    ///
160
    /// Returns `Some(true)` if there is a match, `Some(false)` if there is no match, or `None` if
161
    /// it wasn't possible to know (more data is needed).
162
    ///
163
    /// The custom comparison function is used elsewhere to compare ascii-case-insensitively.
164
    ///
165
    /// # Examples
166
    ///
167
    /// ```
168
    /// # extern crate markup5ever;
169
    /// # #[macro_use] extern crate tendril;
170
    /// # fn main() {
171
    /// use markup5ever::buffer_queue::BufferQueue;
172
    ///
173
    /// let mut queue = BufferQueue::default();
174
    /// queue.push_back(format_tendril!("testtext"));
175
    /// let test_str = "test";
176
    /// assert_eq!(queue.eat("test", |&a, &b| a == b), Some(true));
177
    /// assert_eq!(queue.eat("text", |&a, &b| a == b), Some(true));
178
    /// assert!(queue.is_empty());
179
    /// # }
180
    /// ```
181
0
    pub fn eat<F: Fn(&u8, &u8) -> bool>(&mut self, pat: &str, eq: F) -> Option<bool> {
182
0
        let mut buffers_exhausted = 0;
183
0
        let mut consumed_from_last = 0;
184
0
185
0
        self.buffers.front()?;
186
187
0
        for pattern_byte in pat.bytes() {
188
0
            if buffers_exhausted >= self.buffers.len() {
189
0
                return None;
190
0
            }
191
0
            let buf = &self.buffers[buffers_exhausted];
192
0
193
0
            if !eq(&buf.as_bytes()[consumed_from_last], &pattern_byte) {
194
0
                return Some(false);
195
0
            }
196
0
197
0
            consumed_from_last += 1;
198
0
            if consumed_from_last >= buf.len() {
199
0
                buffers_exhausted += 1;
200
0
                consumed_from_last = 0;
201
0
            }
202
        }
203
204
        // We have a match. Commit changes to the BufferQueue.
205
0
        for _ in 0..buffers_exhausted {
206
0
            self.buffers.pop_front();
207
0
        }
208
209
0
        match self.buffers.front_mut() {
210
0
            None => assert_eq!(consumed_from_last, 0),
211
0
            Some(ref mut buf) => buf.pop_front(consumed_from_last as u32),
212
        }
213
214
0
        Some(true)
215
0
    }
Unexecuted instantiation: <markup5ever::util::buffer_queue::BufferQueue>::eat::<for<'a, 'b> fn(&'a u8, &'b u8) -> bool>
Unexecuted instantiation: <markup5ever::util::buffer_queue::BufferQueue>::eat::<_>
216
}
217
218
impl Iterator for BufferQueue {
219
    type Item = char;
220
221
    /// Get the next character if one is available, removing it from the queue.
222
    ///
223
    /// This function manages the buffers, removing them as they become empty.
224
0
    fn next(&mut self) -> Option<char> {
225
0
        let (result, now_empty) = match self.buffers.front_mut() {
226
0
            None => (None, false),
227
0
            Some(buf) => {
228
0
                let c = buf.pop_front_char().expect("empty buffer in queue");
229
0
                (Some(c), buf.is_empty())
230
            },
231
        };
232
233
0
        if now_empty {
234
0
            self.buffers.pop_front();
235
0
        }
236
237
0
        result
238
0
    }
239
}
240
241
#[cfg(test)]
242
#[allow(non_snake_case)]
243
mod test {
244
    use tendril::SliceExt;
245
246
    use super::BufferQueue;
247
    use super::SetResult::{FromSet, NotFromSet};
248
249
    #[test]
250
    fn smoke_test() {
251
        let mut bq = BufferQueue::default();
252
        assert_eq!(bq.peek(), None);
253
        assert_eq!(bq.next(), None);
254
255
        bq.push_back("abc".to_tendril());
256
        assert_eq!(bq.peek(), Some('a'));
257
        assert_eq!(bq.next(), Some('a'));
258
        assert_eq!(bq.peek(), Some('b'));
259
        assert_eq!(bq.peek(), Some('b'));
260
        assert_eq!(bq.next(), Some('b'));
261
        assert_eq!(bq.peek(), Some('c'));
262
        assert_eq!(bq.next(), Some('c'));
263
        assert_eq!(bq.peek(), None);
264
        assert_eq!(bq.next(), None);
265
    }
266
267
    #[test]
268
    fn can_unconsume() {
269
        let mut bq = BufferQueue::default();
270
        bq.push_back("abc".to_tendril());
271
        assert_eq!(bq.next(), Some('a'));
272
273
        bq.push_front("xy".to_tendril());
274
        assert_eq!(bq.next(), Some('x'));
275
        assert_eq!(bq.next(), Some('y'));
276
        assert_eq!(bq.next(), Some('b'));
277
        assert_eq!(bq.next(), Some('c'));
278
        assert_eq!(bq.next(), None);
279
    }
280
281
    #[test]
282
    fn can_pop_except_set() {
283
        let mut bq = BufferQueue::default();
284
        bq.push_back("abc&def".to_tendril());
285
        let mut pop = || bq.pop_except_from(small_char_set!('&'));
286
        assert_eq!(pop(), Some(NotFromSet("abc".to_tendril())));
287
        assert_eq!(pop(), Some(FromSet('&')));
288
        assert_eq!(pop(), Some(NotFromSet("def".to_tendril())));
289
        assert_eq!(pop(), None);
290
    }
291
292
    #[test]
293
    fn can_eat() {
294
        // This is not very comprehensive.  We rely on the tokenizer
295
        // integration tests for more thorough testing with many
296
        // different input buffer splits.
297
        let mut bq = BufferQueue::default();
298
        bq.push_back("a".to_tendril());
299
        bq.push_back("bc".to_tendril());
300
        assert_eq!(bq.eat("abcd", u8::eq_ignore_ascii_case), None);
301
        assert_eq!(bq.eat("ax", u8::eq_ignore_ascii_case), Some(false));
302
        assert_eq!(bq.eat("ab", u8::eq_ignore_ascii_case), Some(true));
303
        assert_eq!(bq.next(), Some('c'));
304
        assert_eq!(bq.next(), None);
305
    }
306
}