Skip to main content

scah/utils/
reader.rs

1use std::ops::Range;
2
3pub struct Reader<'a> {
4    source: &'a [u8],
5    position: usize,
6}
7
8impl<'a> Reader<'a> {
9    pub fn new(input: &'a str) -> Self {
10        Self {
11            source: input.as_bytes(),
12            position: 0,
13        }
14    }
15
16    pub fn from_bytes(input: &'a [u8]) -> Self {
17        Self {
18            source: input,
19            position: 0,
20        }
21    }
22
23    #[inline]
24    pub fn get_position(&self) -> usize {
25        self.position
26    }
27
28    #[inline]
29    pub fn slice(&self, range: Range<usize>) -> &'a str {
30        // SAFETY: The source was originally a &str, and structural characters are ASCII.
31        // We should be careful about slicing in the middle of a UTF-8 character.
32        unsafe { std::str::from_utf8_unchecked(&self.source[range]) }
33    }
34
35    #[inline]
36    pub fn peek(&self) -> Option<u8> {
37        self.source.get(self.position).copied()
38    }
39
40    #[inline]
41    pub fn next_while_list(&mut self, characters: &[u8]) {
42        let len = self.source.len();
43        while self.position < len && characters.contains(&self.source[self.position]) {
44            self.position += 1;
45        }
46    }
47
48    #[inline]
49    pub fn next_while(&mut self, character: u8) {
50        let len = self.source.len();
51        while self.position < len && self.source[self.position] == character {
52            self.position += 1;
53        }
54    }
55
56    #[inline]
57    pub fn next_until_list(&mut self, characters: &[u8]) {
58        let len = self.source.len();
59        while self.position < len && !characters.contains(&self.source[self.position]) {
60            self.position += 1;
61        }
62    }
63
64    // move cursor to <character> position
65    pub fn next_until(&mut self, character: u8) {
66        let len = self.source.len();
67        while self.position < len && self.source[self.position] != character {
68            self.position += 1;
69        }
70    }
71
72    pub fn skip(&mut self) {
73        if self.position < self.source.len() {
74            self.position += 1;
75        }
76    }
77
78    pub fn eof(&self) -> bool {
79        if self.position >= self.source.len() {
80            return true;
81        }
82
83        self.source[self.position..]
84            .iter()
85            .all(|b| b.is_ascii_whitespace())
86    }
87
88    pub fn match_ignore_case(&self, s: &str) -> bool {
89        if self.position + s.len() > self.source.len() {
90            return false;
91        }
92        let slice = &self.source[self.position..self.position + s.len()];
93        slice.eq_ignore_ascii_case(s.as_bytes())
94    }
95}
96
97impl<'a> Iterator for Reader<'a> {
98    type Item = u8;
99
100    #[inline]
101    fn next(&mut self) -> Option<Self::Item> {
102        if self.position < self.source.len() {
103            let b = self.source[self.position];
104            self.position += 1;
105            Some(b)
106        } else {
107            None
108        }
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115
116    #[test]
117    fn basic_iterator() {
118        let my_string = String::from("Hello World");
119        let mut reader = Reader::new(&my_string);
120
121        assert_eq!(reader.get_position(), 0);
122        assert_eq!(reader.next(), Some(b'H'));
123
124        assert_eq!(reader.get_position(), 1);
125        assert_eq!(reader.next(), Some(b'e'));
126
127        assert_eq!(reader.get_position(), 2);
128        assert_eq!(reader.next(), Some(b'l'));
129
130        assert_eq!(reader.get_position(), 3);
131        assert_eq!(reader.next(), Some(b'l'));
132
133        assert_eq!(reader.get_position(), 4);
134        assert_eq!(reader.next(), Some(b'o'));
135
136        assert_eq!(reader.get_position(), 5);
137        assert_eq!(reader.next(), Some(b' '));
138
139        assert_eq!(reader.get_position(), 6);
140        assert_eq!(reader.next(), Some(b'W'));
141
142        assert_eq!(reader.get_position(), 7);
143        assert_eq!(reader.next(), Some(b'o'));
144
145        assert_eq!(reader.get_position(), 8);
146        assert_eq!(reader.next(), Some(b'r'));
147
148        assert_eq!(reader.get_position(), 9);
149        assert_eq!(reader.next(), Some(b'l'));
150
151        assert_eq!(reader.get_position(), 10);
152        assert_eq!(reader.peek(), Some(b'd'));
153
154        assert_eq!(reader.get_position(), 10);
155        assert_eq!(reader.next(), Some(b'd'));
156
157        assert_eq!(reader.slice(0..5), "Hello");
158    }
159}