saphyr_parser/input/buffered.rs
1use crate::char_traits::is_breakz;
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}
33
34impl<T: Iterator<Item = char>> BufferedInput<T> {
35 /// Create a new [`BufferedInput`] with the given input.
36 pub fn new(input: T) -> Self {
37 Self {
38 input,
39 buffer: ArrayDeque::default(),
40 }
41 }
42}
43
44impl<T: Iterator<Item = char>> Input for BufferedInput<T> {
45 #[inline]
46 fn lookahead(&mut self, count: usize) {
47 if self.buffer.len() >= count {
48 return;
49 }
50 for _ in 0..(count - self.buffer.len()) {
51 self.buffer
52 .push_back(self.input.next().unwrap_or('\0'))
53 .unwrap();
54 }
55 }
56
57 #[inline]
58 fn buflen(&self) -> usize {
59 self.buffer.len()
60 }
61
62 #[inline]
63 fn bufmaxlen(&self) -> usize {
64 BUFFER_LEN
65 }
66
67 #[inline]
68 fn raw_read_ch(&mut self) -> char {
69 self.input.next().unwrap_or('\0')
70 }
71
72 #[inline]
73 fn raw_read_non_breakz_ch(&mut self) -> Option<char> {
74 if let Some(c) = self.input.next() {
75 if is_breakz(c) {
76 self.buffer.push_back(c).unwrap();
77 None
78 } else {
79 Some(c)
80 }
81 } else {
82 None
83 }
84 }
85
86 #[inline]
87 fn skip(&mut self) {
88 self.buffer.pop_front();
89 }
90
91 #[inline]
92 fn skip_n(&mut self, count: usize) {
93 self.buffer.drain(0..count);
94 }
95
96 #[inline]
97 fn peek(&self) -> char {
98 self.buffer[0]
99 }
100
101 #[inline]
102 fn peek_nth(&self, n: usize) -> char {
103 self.buffer[n]
104 }
105}