1use std::str::FromStr;
2
3use crate::Error;
4
5pub(crate) trait ByteExt {
7 fn is_sign(&self) -> bool;
9
10 fn is_digit(&self) -> bool;
14
15 fn is_hex_digit(&self) -> bool;
19
20 fn is_space(&self) -> bool;
24
25 fn is_ascii_ident(&self) -> bool;
27}
28impl ByteExt for u8 {
29 #[inline]
30 fn is_sign(&self) -> bool {
31 matches!(*self, b'+' | b'-')
32 }
33
34 #[inline]
35 fn is_digit(&self) -> bool {
36 matches!(*self, b'0'..=b'9')
37 }
38
39 #[inline]
40 fn is_hex_digit(&self) -> bool {
41 matches!(*self, b'0'..=b'9' | b'A'..=b'F' | b'a'..=b'f')
42 }
43
44 #[inline]
45 fn is_space(&self) -> bool {
46 matches!(*self, b' ' | b'\t' | b'\n' | b'\r')
47 }
48
49 #[inline]
50 fn is_ascii_ident(&self) -> bool {
51 matches!(*self, b'0'..=b'9' | b'A'..=b'Z' | b'a'..=b'z' | b'-' | b'_')
52 }
53}
54
55#[derive(Clone, Copy, PartialEq, Eq, Debug)]
57pub struct Stream<'a> {
58 text: &'a str,
59 pos: usize,
60}
61
62impl<'a> From<&'a str> for Stream<'a> {
63 #[inline]
64 fn from(text: &'a str) -> Self {
65 Stream { text, pos: 0 }
66 }
67}
68
69impl<'a> Stream<'a> {
70 #[inline]
72 pub fn pos(&self) -> usize {
73 self.pos
74 }
75
76 pub fn calc_char_pos(&self) -> usize {
78 self.calc_char_pos_at(self.pos)
79 }
80
81 pub fn calc_char_pos_at(&self, byte_pos: usize) -> usize {
83 let mut pos = 1;
84 for (idx, _) in self.text.char_indices() {
85 if idx >= byte_pos {
86 break;
87 }
88
89 pos += 1;
90 }
91
92 pos
93 }
94
95 #[inline]
99 pub fn jump_to_end(&mut self) {
100 self.pos = self.text.len();
101 }
102
103 #[inline]
115 pub fn at_end(&self) -> bool {
116 self.pos >= self.text.len()
117 }
118
119 #[inline]
125 pub fn curr_byte(&self) -> Result<u8, Error> {
126 if self.at_end() {
127 return Err(Error::UnexpectedEndOfStream);
128 }
129
130 Ok(self.curr_byte_unchecked())
131 }
132
133 #[inline]
134 pub fn chars(&self) -> std::str::Chars<'a> {
135 self.text[self.pos..].chars()
136 }
137
138 #[inline]
144 pub fn curr_byte_unchecked(&self) -> u8 {
145 self.text.as_bytes()[self.pos]
146 }
147
148 #[inline]
152 pub fn is_curr_byte_eq(&self, c: u8) -> bool {
153 if !self.at_end() {
154 self.curr_byte_unchecked() == c
155 } else {
156 false
157 }
158 }
159
160 #[inline]
166 pub fn next_byte(&self) -> Result<u8, Error> {
167 if self.pos + 1 >= self.text.len() {
168 return Err(Error::UnexpectedEndOfStream);
169 }
170
171 Ok(self.text.as_bytes()[self.pos + 1])
172 }
173
174 #[inline]
176 pub fn advance(&mut self, n: usize) {
177 debug_assert!(self.pos + n <= self.text.len());
178 self.pos += n;
179 }
180
181 pub fn skip_spaces(&mut self) {
185 while !self.at_end() && self.curr_byte_unchecked().is_space() {
186 self.advance(1);
187 }
188 }
189
190 #[inline]
194 pub fn starts_with(&self, text: &[u8]) -> bool {
195 self.text.as_bytes()[self.pos..].starts_with(text)
196 }
197
198 pub fn consume_byte(&mut self, c: u8) -> Result<(), Error> {
205 if self.curr_byte()? != c {
206 return Err(Error::InvalidChar(
207 vec![self.curr_byte_unchecked(), c],
208 self.calc_char_pos(),
209 ));
210 }
211
212 self.advance(1);
213 Ok(())
214 }
215
216 pub fn consume_ascii_ident(&mut self) -> &'a str {
219 let start = self.pos;
220 self.skip_bytes(|_, c| c.is_ascii_ident());
221 self.slice_back(start)
222 }
223
224 pub fn parse_quoted_string(&mut self) -> Result<&'a str, Error> {
231 let quote = self.curr_byte()?;
233
234 if quote != b'\'' && quote != b'"' {
235 return Err(Error::InvalidValue);
236 }
237
238 let mut prev = quote;
239 self.advance(1);
240
241 let start = self.pos();
242
243 while !self.at_end() {
244 let curr = self.curr_byte_unchecked();
245
246 if curr == quote {
248 if prev != b'\\' {
250 break;
251 }
252 }
253
254 prev = curr;
255 self.advance(1);
256 }
257
258 let value = self.slice_back(start);
259
260 self.consume_byte(quote)?;
262
263 Ok(value)
264 }
265
266 pub fn consume_string(&mut self, text: &[u8]) -> Result<(), Error> {
273 if self.at_end() {
274 return Err(Error::UnexpectedEndOfStream);
275 }
276
277 if !self.starts_with(text) {
278 let len = std::cmp::min(text.len(), self.text.len() - self.pos);
279 let actual = self.text[self.pos..].chars().take(len).collect();
283
284 let expected = std::str::from_utf8(text).unwrap().to_owned();
286
287 return Err(Error::InvalidString(
288 vec![actual, expected],
289 self.calc_char_pos(),
290 ));
291 }
292
293 self.advance(text.len());
294 Ok(())
295 }
296
297 pub fn consume_bytes<F>(&mut self, f: F) -> &'a str
301 where
302 F: Fn(&Stream, u8) -> bool,
303 {
304 let start = self.pos();
305 self.skip_bytes(f);
306 self.slice_back(start)
307 }
308
309 pub fn skip_bytes<F>(&mut self, f: F)
311 where
312 F: Fn(&Stream, u8) -> bool,
313 {
314 while !self.at_end() {
315 let c = self.curr_byte_unchecked();
316 if f(self, c) {
317 self.advance(1);
318 } else {
319 break;
320 }
321 }
322 }
323
324 #[inline]
326 pub fn slice_back(&self, pos: usize) -> &'a str {
327 &self.text[pos..self.pos]
328 }
329
330 #[inline]
332 pub fn slice_tail(&self) -> &'a str {
333 &self.text[self.pos..]
334 }
335
336 pub fn parse_integer(&mut self) -> Result<i32, Error> {
342 self.skip_spaces();
343
344 if self.at_end() {
345 return Err(Error::InvalidNumber(self.calc_char_pos()));
346 }
347
348 let start = self.pos();
349
350 if self.curr_byte()?.is_sign() {
352 self.advance(1);
353 }
354
355 if !self.curr_byte()?.is_digit() {
357 return Err(Error::InvalidNumber(self.calc_char_pos_at(start)));
358 }
359
360 self.skip_digits();
361
362 let s = self.slice_back(start);
364 match i32::from_str(s) {
365 Ok(n) => Ok(n),
366 Err(_) => Err(Error::InvalidNumber(self.calc_char_pos_at(start))),
367 }
368 }
369
370 pub fn parse_list_integer(&mut self) -> Result<i32, Error> {
372 if self.at_end() {
373 return Err(Error::UnexpectedEndOfStream);
374 }
375
376 let n = self.parse_integer()?;
377 self.skip_spaces();
378 self.parse_list_separator();
379 Ok(n)
380 }
381
382 pub fn parse_number_or_percent(&mut self) -> Result<f64, Error> {
386 self.skip_spaces();
387
388 let n = self.parse_number()?;
389 if self.starts_with(b"%") {
390 self.advance(1);
391 Ok(n / 100.0)
392 } else {
393 Ok(n)
394 }
395 }
396
397 pub fn parse_list_number_or_percent(&mut self) -> Result<f64, Error> {
399 if self.at_end() {
400 return Err(Error::UnexpectedEndOfStream);
401 }
402
403 let l = self.parse_number_or_percent()?;
404 self.skip_spaces();
405 self.parse_list_separator();
406 Ok(l)
407 }
408
409 pub fn skip_digits(&mut self) {
411 self.skip_bytes(|_, c| c.is_digit());
412 }
413
414 #[inline]
415 pub(crate) fn parse_list_separator(&mut self) {
416 if self.is_curr_byte_eq(b',') {
417 self.advance(1);
418 }
419 }
420}
421
422#[rustfmt::skip]
423#[cfg(test)]
424mod tests {
425 use super::*;
426
427 #[test]
428 fn parse_integer_1() {
429 let mut s = Stream::from("10");
430 assert_eq!(s.parse_integer().unwrap(), 10);
431 }
432
433 #[test]
434 fn parse_err_integer_1() {
435 let mut s = Stream::from("10000000000000");
437 assert_eq!(s.parse_integer().unwrap_err().to_string(),
438 "invalid number at position 1");
439 }
440}