multi_line_stream/
lib.rs

1#[cfg(feature = "regex")]
2use regex::Regex;
3
4/// # MultiLineStream
5/// Quick movement on multiple lines of text.
6///
7/// Indexes are measured in bytes
8pub struct MultiLineStream<'a> {
9    pub source: &'a str,
10    position: usize,
11}
12
13impl<'a> MultiLineStream<'a> {
14    pub fn new(source: &'a str, position: usize) -> MultiLineStream<'a> {
15        MultiLineStream { source, position }
16    }
17
18    pub fn eos(&self) -> bool {
19        self.source.len() <= self.position
20    }
21
22    pub fn pos(&self) -> usize {
23        self.position
24    }
25
26    pub fn go_back(&mut self, n: usize) {
27        self.position -= n;
28    }
29
30    pub fn advance(&mut self, n: usize) {
31        self.position += n;
32    }
33
34    pub fn go_to_end(&mut self) {
35        self.position = self.source.len();
36    }
37
38    pub fn peek_char(&self, n: isize) -> Option<u8> {
39        let index = if n >= 0 {
40            self.position + n as usize
41        } else {
42            self.position - (-n) as usize
43        };
44        Some(self.source.bytes().nth(index)?)
45    }
46
47    pub fn advance_if_char(&mut self, ch: u8) -> bool {
48        if let Some(char) = self.source.bytes().nth(self.position) {
49            if char == ch {
50                self.position += 1;
51                return true;
52            }
53        }
54        false
55    }
56
57    pub fn advance_if_chars(&mut self, ch: &str) -> bool {
58        if self.position + ch.len() > self.source.len() {
59            return false;
60        }
61
62        if !self
63            .source
64            .get(self.position..self.position + ch.len())
65            .is_some_and(|v| v == ch)
66        {
67            return false;
68        }
69
70        self.advance(ch.len());
71        true
72    }
73
74    #[cfg(feature = "regex")]
75    pub fn advance_if_regexp(&mut self, regexp: &Regex) -> Option<&'a str> {
76        let haystack = &self.source[self.position..];
77        let captures = regexp.captures(haystack)?;
78        let m = captures.get(0).unwrap();
79        self.position += m.end();
80        Some(m.as_str())
81    }
82
83    #[cfg(feature = "regex")]
84    pub fn advance_until_regexp(&mut self, regexp: &Regex) -> Option<&'a str> {
85        let haystack = &self.source[self.position..];
86        if let Some(captures) = regexp.captures(haystack) {
87            let m = captures.get(0).unwrap();
88            self.position += m.start();
89            Some(m.as_str())
90        } else {
91            self.go_to_end();
92            None
93        }
94    }
95
96    pub fn advance_until_char(&mut self, ch: u8) -> bool {
97        while self.position < self.source.len() {
98            if self.source.bytes().nth(self.position) == Some(ch) {
99                return true;
100            }
101            self.advance(1);
102        }
103        false
104    }
105
106    pub fn advance_until_chars(&mut self, ch: &str) -> bool {
107        while self.position + ch.len() <= self.source.len() {
108            if self
109                .source
110                .get(self.position..self.position + ch.len())
111                .is_some_and(|v| v == ch)
112            {
113                return true;
114            }
115            self.advance(1);
116        }
117        self.go_to_end();
118        false
119    }
120
121    pub fn skip_whitespace(&mut self) -> bool {
122        let n = self.advance_while_char(|ch| vec![b' ', b'\t', b'\n', 12, b'\r'].contains(&ch));
123        n > 0
124    }
125
126    pub fn advance_while_char<F>(&mut self, condition: F) -> usize
127    where
128        F: Fn(u8) -> bool,
129    {
130        let pos_now = self.position;
131        while self.position < self.source.len()
132            && condition(self.source.bytes().nth(self.position).unwrap())
133        {
134            self.advance(1);
135        }
136        self.position - pos_now
137    }
138}