Skip to main content

saphyr_parser/input/
buffered.rs

1use crate::char_traits::is_yaml_non_break;
2use crate::input::Input;
3
4use arraydeque::ArrayDeque;
5
6/// The size of the [`BufferedInput`] buffer.
7///
8/// The buffer is statically allocated to avoid conditions for reallocations each time we
9/// consume/push a character. As of now, almost all lookaheads are 4 characters maximum, except:
10///   - Escape sequences parsing: some escape codes are 8 characters
11///   - Scanning indent in scalars: this looks ahead `indent + 2` characters
12///
13/// This constant must be set to at least 8. When scanning indent in scalars, the lookahead is done
14/// in a single call if and only if the indent is `BUFFER_LEN - 2` or less. If the indent is higher
15/// than that, the code will fall back to a loop of lookaheads.
16const BUFFER_LEN: usize = 16;
17
18/// A wrapper around an [`Iterator`] of [`char`]s with a buffer.
19///
20/// The YAML scanner often needs some lookahead. With fully allocated buffers such as `String` or
21/// `&str`, this is not an issue. However, with streams, we need to have a way of peeking multiple
22/// characters at a time and sometimes pushing some back into the stream.
23/// There is no "easy" way of doing this without itertools. In order to avoid pulling the entierty
24/// of itertools for one method, we use this structure.
25#[allow(clippy::module_name_repetitions)]
26#[derive(Clone)]
27pub struct BufferedInput<T: Iterator<Item = char>> {
28    /// The iterator source,
29    input: T,
30    /// Buffer for the next characters to consume.
31    buffer: ArrayDeque<char, BUFFER_LEN>,
32    /// Number of NUL padding characters currently held at the end of `buffer`.
33    padding: usize,
34}
35
36impl<T: Iterator<Item = char>> BufferedInput<T> {
37    /// Create a new [`BufferedInput`] with the given input.
38    pub fn new(input: T) -> Self {
39        Self {
40            input,
41            buffer: ArrayDeque::default(),
42            padding: 0,
43        }
44    }
45}
46
47impl<T: Iterator<Item = char>> Input for BufferedInput<T> {
48    #[inline]
49    fn lookahead(&mut self, count: usize) {
50        if self.buffer.len() >= count {
51            return;
52        }
53        for _ in 0..(count - self.buffer.len()) {
54            if let Some(c) = self.input.next() {
55                self.buffer.push_back(c).unwrap();
56            } else {
57                self.padding += 1;
58                self.buffer.push_back('\0').unwrap();
59            }
60        }
61    }
62
63    #[inline]
64    fn buflen(&self) -> usize {
65        self.buffer.len()
66    }
67
68    #[inline]
69    fn bufmaxlen(&self) -> usize {
70        BUFFER_LEN
71    }
72
73    #[inline]
74    fn raw_read_ch(&mut self) -> char {
75        self.input.next().unwrap_or('\0')
76    }
77
78    #[inline]
79    fn raw_read_non_breakz_ch(&mut self) -> Option<char> {
80        if let Some(c) = self.input.next() {
81            if is_yaml_non_break(c) {
82                Some(c)
83            } else {
84                self.buffer.push_back(c).unwrap();
85                None
86            }
87        } else {
88            None
89        }
90    }
91
92    #[inline]
93    fn skip(&mut self) {
94        if self.buffer.len() == self.padding {
95            self.padding = self.padding.saturating_sub(1);
96        }
97        self.buffer.pop_front();
98    }
99
100    #[inline]
101    fn skip_n(&mut self, count: usize) {
102        self.buffer.drain(0..count);
103        if self.buffer.len() < self.padding {
104            self.padding = self.buffer.len();
105        }
106    }
107
108    #[inline]
109    fn peek(&self) -> char {
110        self.buffer[0]
111    }
112
113    #[inline]
114    fn peek_nth(&self, n: usize) -> char {
115        self.buffer[n]
116    }
117
118    #[inline]
119    fn peek_opt(&self) -> Option<char> {
120        if self.buffer.len() > self.padding {
121            Some(self.buffer[0])
122        } else {
123            None
124        }
125    }
126}