Skip to main content

saphyr_parser/input/
str.rs

1use crate::{
2    char_traits::{
3        is_alpha, is_blank, is_blank_or_breakz, is_break, is_breakz, is_digit, is_flow,
4        is_yaml_non_break, is_z,
5    },
6    input::{Input, SkipTabs},
7};
8use alloc::string::String;
9
10/// A parser input that uses a `&str` as source.
11#[allow(clippy::module_name_repetitions)]
12#[derive(Clone, Copy)]
13pub struct StrInput<'a> {
14    /// The input str buffer.
15    buffer: &'a str,
16    /// The number of characters we have looked ahead.
17    ///
18    /// We must however keep track of how many characters the parser asked us to look ahead for so
19    /// that we can return the correct value in [`Self::buflen`].
20    lookahead: usize,
21}
22
23impl<'a> StrInput<'a> {
24    /// Create a new [`StrInput`] with the given str.
25    #[must_use]
26    pub fn new(input: &'a str) -> Self {
27        Self {
28            buffer: input,
29            lookahead: 0,
30        }
31    }
32}
33
34impl Input for StrInput<'_> {
35    #[inline]
36    fn lookahead(&mut self, x: usize) {
37        // We already have all characters that we need.
38        // We cannot add '\0's to the buffer should we prematurely reach EOF.
39        // Returning '\0's befalls the character-retrieving functions.
40        self.lookahead = self.lookahead.max(x);
41    }
42
43    #[inline]
44    fn buflen(&self) -> usize {
45        self.lookahead
46    }
47
48    #[inline]
49    fn bufmaxlen(&self) -> usize {
50        BUFFER_LEN
51    }
52
53    fn buf_is_empty(&self) -> bool {
54        self.buflen() == 0
55    }
56
57    #[inline]
58    fn raw_read_ch(&mut self) -> char {
59        let mut chars = self.buffer.chars();
60        if let Some(c) = chars.next() {
61            self.buffer = chars.as_str();
62            c
63        } else {
64            '\0'
65        }
66    }
67
68    #[inline]
69    fn raw_read_non_breakz_ch(&mut self) -> Option<char> {
70        if let Some((c, sub_str)) = split_first_char(self.buffer) {
71            if is_yaml_non_break(c) {
72                self.buffer = sub_str;
73                Some(c)
74            } else {
75                None
76            }
77        } else {
78            None
79        }
80    }
81
82    #[inline]
83    fn skip(&mut self) {
84        let mut chars = self.buffer.chars();
85        if chars.next().is_some() {
86            self.buffer = chars.as_str();
87        }
88    }
89
90    #[inline]
91    fn skip_n(&mut self, count: usize) {
92        let mut chars = self.buffer.chars();
93        for _ in 0..count {
94            if chars.next().is_none() {
95                break;
96            }
97        }
98        self.buffer = chars.as_str();
99    }
100
101    #[inline]
102    fn peek(&self) -> char {
103        self.buffer.chars().next().unwrap_or('\0')
104    }
105
106    #[inline]
107    fn peek_nth(&self, n: usize) -> char {
108        let mut chars = self.buffer.chars();
109        for _ in 0..n {
110            if chars.next().is_none() {
111                return '\0';
112            }
113        }
114        chars.next().unwrap_or('\0')
115    }
116
117    #[inline]
118    fn peek_opt(&self) -> Option<char> {
119        self.buffer.chars().next()
120    }
121
122    #[inline]
123    fn look_ch(&mut self) -> char {
124        self.lookahead(1);
125        self.peek()
126    }
127
128    #[inline]
129    fn next_char_is(&self, c: char) -> bool {
130        self.peek() == c
131    }
132
133    #[inline]
134    fn nth_char_is(&self, n: usize, c: char) -> bool {
135        self.peek_nth(n) == c
136    }
137
138    #[inline]
139    fn next_2_are(&self, c1: char, c2: char) -> bool {
140        let mut chars = self.buffer.chars();
141        chars.next() == Some(c1) && chars.next() == Some(c2)
142    }
143
144    #[inline]
145    fn next_3_are(&self, c1: char, c2: char, c3: char) -> bool {
146        let mut chars = self.buffer.chars();
147        chars.next() == Some(c1) && chars.next() == Some(c2) && chars.next() == Some(c3)
148    }
149
150    #[inline]
151    fn next_is_document_indicator(&self) -> bool {
152        if self.buffer.len() < 3 {
153            false
154        } else {
155            // Since all characters we look for are ascii, we can directly use the byte API of str.
156            let bytes = self.buffer.as_bytes();
157            (bytes.len() == 3 || is_blank_or_breakz(bytes[3] as char))
158                && (bytes[0] == b'.' || bytes[0] == b'-')
159                && bytes[0] == bytes[1]
160                && bytes[1] == bytes[2]
161        }
162    }
163
164    #[inline]
165    fn next_is_document_start(&self) -> bool {
166        if self.buffer.len() < 3 {
167            false
168        } else {
169            // Since all characters we look for are ascii, we can directly use the byte API of str.
170            let bytes = self.buffer.as_bytes();
171            (bytes.len() == 3 || is_blank_or_breakz(bytes[3] as char))
172                && bytes[0] == b'-'
173                && bytes[1] == b'-'
174                && bytes[2] == b'-'
175        }
176    }
177
178    #[inline]
179    fn next_is_document_end(&self) -> bool {
180        if self.buffer.len() < 3 {
181            false
182        } else {
183            // Since all characters we look for are ascii, we can directly use the byte API of str.
184            let bytes = self.buffer.as_bytes();
185            (bytes.len() == 3 || is_blank_or_breakz(bytes[3] as char))
186                && bytes[0] == b'.'
187                && bytes[1] == b'.'
188                && bytes[2] == b'.'
189        }
190    }
191
192    fn skip_ws_to_eol(&mut self, skip_tabs: SkipTabs) -> (usize, Result<SkipTabs, &'static str>) {
193        assert!(!matches!(skip_tabs, SkipTabs::Result(..)));
194
195        let mut new_str = self.buffer;
196        let mut has_yaml_ws = false;
197        let mut encountered_tab = false;
198
199        // This ugly pair of loops is the fastest way of trimming spaces (and maybe tabs) I found
200        // while keeping track of whether we encountered spaces and/or tabs.
201        if skip_tabs == SkipTabs::Yes {
202            loop {
203                if let Some(sub_str) = new_str.strip_prefix(' ') {
204                    has_yaml_ws = true;
205                    new_str = sub_str;
206                } else if let Some(sub_str) = new_str.strip_prefix('\t') {
207                    encountered_tab = true;
208                    new_str = sub_str;
209                } else {
210                    break;
211                }
212            }
213        } else {
214            while let Some(sub_str) = new_str.strip_prefix(' ') {
215                has_yaml_ws = true;
216                new_str = sub_str;
217            }
218        }
219
220        // All characters consumed were ascii. We can use the byte length difference to count the
221        // number of whitespace ignored.
222        let mut chars_consumed = self.buffer.len() - new_str.len();
223
224        if !new_str.is_empty() && new_str.as_bytes()[0] == b'#' {
225            if !encountered_tab && !has_yaml_ws {
226                return (
227                    chars_consumed,
228                    Err("comments must be separated from other tokens by whitespace"),
229                );
230            }
231
232            // Skip remaining non-break characters.
233            while let Some((c, sub_str)) = split_first_char(new_str) {
234                if !is_yaml_non_break(c) {
235                    break;
236                }
237                new_str = sub_str;
238                chars_consumed += 1;
239            }
240        }
241
242        self.buffer = new_str;
243
244        (
245            chars_consumed,
246            Ok(SkipTabs::Result(encountered_tab, has_yaml_ws)),
247        )
248    }
249
250    #[allow(clippy::inline_always)]
251    #[inline(always)]
252    fn next_can_be_plain_scalar(&self, in_flow: bool) -> bool {
253        let nc = self.peek_nth(1);
254        match self.peek() {
255            // indicators can end a plain scalar, see 7.3.3. Plain Style
256            ':' if is_blank_or_breakz(nc) || (in_flow && is_flow(nc)) => false,
257            // A byte order mark isn't a plain scalar character, see 5.2. Character Encodings
258            '\u{FEFF}' => false,
259            c if in_flow && is_flow(c) => false,
260            _ => true,
261        }
262    }
263
264    #[inline]
265    fn next_is_blank_or_break(&self) -> bool {
266        !self.buffer.is_empty()
267            && (is_blank(self.buffer.as_bytes()[0] as char)
268                || is_break(self.buffer.as_bytes()[0] as char))
269    }
270
271    #[inline]
272    fn next_is_blank_or_breakz(&self) -> bool {
273        self.buffer.is_empty()
274            || (is_blank(self.buffer.as_bytes()[0] as char)
275                || is_breakz(self.buffer.as_bytes()[0] as char))
276    }
277
278    #[inline]
279    fn next_is_blank(&self) -> bool {
280        !self.buffer.is_empty() && is_blank(self.buffer.as_bytes()[0] as char)
281    }
282
283    #[inline]
284    fn next_is_break(&self) -> bool {
285        !self.buffer.is_empty() && is_break(self.buffer.as_bytes()[0] as char)
286    }
287
288    #[inline]
289    fn next_is_breakz(&self) -> bool {
290        self.buffer.is_empty() || is_breakz(self.buffer.as_bytes()[0] as char)
291    }
292
293    #[inline]
294    fn next_is_z(&self) -> bool {
295        self.buffer.is_empty() || is_z(self.buffer.as_bytes()[0] as char)
296    }
297
298    #[inline]
299    fn next_is_flow(&self) -> bool {
300        !self.buffer.is_empty() && is_flow(self.buffer.as_bytes()[0] as char)
301    }
302
303    #[inline]
304    fn next_is_digit(&self) -> bool {
305        !self.buffer.is_empty() && is_digit(self.buffer.as_bytes()[0] as char)
306    }
307
308    #[inline]
309    fn next_is_alpha(&self) -> bool {
310        !self.buffer.is_empty() && is_alpha(self.buffer.as_bytes()[0] as char)
311    }
312
313    fn skip_while_non_breakz(&mut self) -> usize {
314        let mut new_str = self.buffer;
315        let mut count = 0;
316
317        // Skip over all non-breaks.
318        while let Some((c, sub_str)) = split_first_char(new_str) {
319            if !is_yaml_non_break(c) {
320                break;
321            }
322            new_str = sub_str;
323            count += 1;
324        }
325
326        self.buffer = new_str;
327
328        count
329    }
330
331    fn skip_while_blank(&mut self) -> usize {
332        // Since all characters we look for are ascii, we can directly use the byte API of str.
333        let mut i = 0;
334        while i < self.buffer.len() {
335            if !is_blank(self.buffer.as_bytes()[i] as char) {
336                break;
337            }
338            i += 1;
339        }
340        self.buffer = &self.buffer[i..];
341        i
342    }
343
344    fn fetch_while_is_alpha(&mut self, out: &mut String) -> usize {
345        let mut not_alpha = None;
346
347        // Skip while we have alpha characters.
348        let mut chars = self.buffer.chars();
349        for c in chars.by_ref() {
350            if !is_alpha(c) {
351                not_alpha = Some(c);
352                break;
353            }
354        }
355
356        let remaining_string = if let Some(c) = not_alpha {
357            let n_bytes_read = chars.as_str().as_ptr() as usize - self.buffer.as_ptr() as usize;
358            let last_char_bytes = c.len_utf8();
359            &self.buffer[n_bytes_read - last_char_bytes..]
360        } else {
361            chars.as_str()
362        };
363
364        let n_bytes_to_append = remaining_string.as_ptr() as usize - self.buffer.as_ptr() as usize;
365        out.reserve(n_bytes_to_append);
366        out.push_str(&self.buffer[..n_bytes_to_append]);
367        self.buffer = remaining_string;
368
369        n_bytes_to_append
370    }
371
372    fn fetch_while_is_yaml_non_space(&mut self, out: &mut String) -> usize {
373        let mut not_non_space = None;
374
375        // Skip while we have non-space characters.
376        let mut chars = self.buffer.chars();
377        for c in chars.by_ref() {
378            if !crate::char_traits::is_yaml_non_space(c) {
379                not_non_space = Some(c);
380                break;
381            }
382        }
383
384        let remaining_string = if let Some(c) = not_non_space {
385            let n_bytes_read = chars.as_str().as_ptr() as usize - self.buffer.as_ptr() as usize;
386            let last_char_bytes = c.len_utf8();
387            &self.buffer[n_bytes_read - last_char_bytes..]
388        } else {
389            chars.as_str()
390        };
391
392        let n_bytes_to_append = remaining_string.as_ptr() as usize - self.buffer.as_ptr() as usize;
393        out.reserve(n_bytes_to_append);
394        out.push_str(&self.buffer[..n_bytes_to_append]);
395        self.buffer = remaining_string;
396
397        n_bytes_to_append
398    }
399}
400
401/// The buffer size we return to the scanner.
402///
403/// This does not correspond to any allocated buffer size. In practice, the scanner can withdraw
404/// any character they want. If it's within the input buffer, the given character is returned,
405/// otherwise `\0` is returned.
406///
407/// The number of characters we are asked to retrieve in [`lookahead`] depends on the buffer size
408/// of the input. Our buffer here is virtually unlimited, but the scanner cannot work with that. It
409/// may allocate buffers of its own of the size we return in [`bufmaxlen`] (so we can't return
410/// [`usize::MAX`]). We can't always return the number of characters left either, as the scanner
411/// expects [`buflen`] to return the same value that was given to [`lookahead`] right after its
412/// call.
413///
414/// This create a complex situation where [`bufmaxlen`] influences what value [`lookahead`] is
415/// called with, which in turns dictates what [`buflen`] returns. In order to avoid breaking any
416/// function, we return this constant in [`bufmaxlen`] which, since the input is processed one line
417/// at a time, should fit what we expect to be a good balance between memory consumption and what
418/// we expect the maximum line length to be.
419///
420/// [`lookahead`]: `StrInput::lookahead`
421/// [`bufmaxlen`]: `StrInput::bufmaxlen`
422/// [`buflen`]: `StrInput::buflen`
423const BUFFER_LEN: usize = 128;
424
425/// Splits the first character of the given string and returns it along with the rest of the
426/// string.
427#[inline]
428fn split_first_char(s: &str) -> Option<(char, &str)> {
429    let mut chars = s.chars();
430    let c = chars.next()?;
431    Some((c, chars.as_str()))
432}
433
434#[cfg(test)]
435mod test {
436    use crate::input::Input;
437
438    use super::StrInput;
439
440    #[test]
441    pub fn is_document_start() {
442        let input = StrInput::new("---\n");
443        assert!(input.next_is_document_start());
444        assert!(input.next_is_document_indicator());
445        let input = StrInput::new("---");
446        assert!(input.next_is_document_start());
447        assert!(input.next_is_document_indicator());
448        let input = StrInput::new("...\n");
449        assert!(!input.next_is_document_start());
450        assert!(input.next_is_document_indicator());
451        let input = StrInput::new("--- ");
452        assert!(input.next_is_document_start());
453        assert!(input.next_is_document_indicator());
454    }
455
456    #[test]
457    pub fn is_document_end() {
458        let input = StrInput::new("...\n");
459        assert!(input.next_is_document_end());
460        assert!(input.next_is_document_indicator());
461        let input = StrInput::new("...");
462        assert!(input.next_is_document_end());
463        assert!(input.next_is_document_indicator());
464        let input = StrInput::new("---\n");
465        assert!(!input.next_is_document_end());
466        assert!(input.next_is_document_indicator());
467        let input = StrInput::new("... ");
468        assert!(input.next_is_document_end());
469        assert!(input.next_is_document_indicator());
470    }
471}