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