use std::str::FromStr;
use crate::Error;
pub(crate) trait ByteExt {
fn is_sign(&self) -> bool;
fn is_digit(&self) -> bool;
fn is_hex_digit(&self) -> bool;
fn is_space(&self) -> bool;
fn is_letter(&self) -> bool;
fn is_ident(&self) -> bool;
}
impl ByteExt for u8 {
#[inline]
fn is_sign(&self) -> bool {
matches!(*self, b'+' | b'-')
}
#[inline]
fn is_digit(&self) -> bool {
matches!(*self, b'0'..=b'9')
}
#[inline]
fn is_hex_digit(&self) -> bool {
matches!(*self, b'0'..=b'9' | b'A'..=b'F' | b'a'..=b'f')
}
#[inline]
fn is_space(&self) -> bool {
matches!(*self, b' ' | b'\t' | b'\n' | b'\r')
}
#[inline]
fn is_letter(&self) -> bool {
matches!(*self, b'A'..=b'Z' | b'a'..=b'z')
}
#[inline]
fn is_ident(&self) -> bool {
matches!(*self, b'0'..=b'9' | b'A'..=b'Z' | b'a'..=b'z' | b'-' | b'_')
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Stream<'a> {
text: &'a str,
pos: usize,
}
impl<'a> From<&'a str> for Stream<'a> {
#[inline]
fn from(text: &'a str) -> Self {
Stream { text, pos: 0 }
}
}
impl<'a> Stream<'a> {
#[inline]
pub fn pos(&self) -> usize {
self.pos
}
pub fn calc_char_pos(&self) -> usize {
self.calc_char_pos_at(self.pos)
}
pub fn calc_char_pos_at(&self, byte_pos: usize) -> usize {
let mut pos = 1;
for (idx, _) in self.text.char_indices() {
if idx >= byte_pos {
break;
}
pos += 1;
}
pos
}
#[inline]
pub fn jump_to_end(&mut self) {
self.pos = self.text.len();
}
#[inline]
pub fn at_end(&self) -> bool {
self.pos >= self.text.len()
}
#[inline]
pub fn curr_byte(&self) -> Result<u8, Error> {
if self.at_end() {
return Err(Error::UnexpectedEndOfStream);
}
Ok(self.curr_byte_unchecked())
}
#[inline]
pub fn curr_byte_unchecked(&self) -> u8 {
self.text.as_bytes()[self.pos]
}
#[inline]
pub fn is_curr_byte_eq(&self, c: u8) -> bool {
if !self.at_end() {
self.curr_byte_unchecked() == c
} else {
false
}
}
#[inline]
pub fn next_byte(&self) -> Result<u8, Error> {
if self.pos + 1 >= self.text.len() {
return Err(Error::UnexpectedEndOfStream);
}
Ok(self.text.as_bytes()[self.pos + 1])
}
#[inline]
pub fn advance(&mut self, n: usize) {
debug_assert!(self.pos + n <= self.text.len());
self.pos += n;
}
pub fn skip_spaces(&mut self) {
while !self.at_end() && self.curr_byte_unchecked().is_space() {
self.advance(1);
}
}
#[inline]
pub fn starts_with(&self, text: &[u8]) -> bool {
self.text.as_bytes()[self.pos..].starts_with(text)
}
pub fn consume_byte(&mut self, c: u8) -> Result<(), Error> {
if self.curr_byte()? != c {
return Err(Error::InvalidChar(
vec![self.curr_byte_unchecked(), c],
self.calc_char_pos(),
));
}
self.advance(1);
Ok(())
}
pub fn consume_string(&mut self, text: &[u8]) -> Result<(), Error> {
if self.at_end() {
return Err(Error::UnexpectedEndOfStream);
}
if !self.starts_with(text) {
let len = std::cmp::min(text.len(), self.text.len() - self.pos);
let actual = self.text[self.pos..].chars().take(len).collect();
let expected = std::str::from_utf8(text).unwrap().to_owned();
return Err(Error::InvalidString(
vec![actual, expected],
self.calc_char_pos(),
));
}
self.advance(text.len());
Ok(())
}
pub fn consume_bytes<F>(&mut self, f: F) -> &'a str
where
F: Fn(&Stream, u8) -> bool,
{
let start = self.pos();
self.skip_bytes(f);
self.slice_back(start)
}
pub fn skip_bytes<F>(&mut self, f: F)
where
F: Fn(&Stream, u8) -> bool,
{
while !self.at_end() {
let c = self.curr_byte_unchecked();
if f(self, c) {
self.advance(1);
} else {
break;
}
}
}
pub fn consume_ident(&mut self) -> &'a str {
let start = self.pos;
self.skip_bytes(|_, c| c.is_ident());
self.slice_back(start)
}
#[inline]
pub fn slice_back(&self, pos: usize) -> &'a str {
&self.text[pos..self.pos]
}
#[inline]
pub fn slice_tail(&self) -> &'a str {
&self.text[self.pos..]
}
pub fn parse_integer(&mut self) -> Result<i32, Error> {
self.skip_spaces();
if self.at_end() {
return Err(Error::InvalidNumber(self.calc_char_pos()));
}
let start = self.pos();
if self.curr_byte()?.is_sign() {
self.advance(1);
}
if !self.curr_byte()?.is_digit() {
return Err(Error::InvalidNumber(self.calc_char_pos_at(start)));
}
self.skip_digits();
let s = self.slice_back(start);
match i32::from_str(s) {
Ok(n) => Ok(n),
Err(_) => Err(Error::InvalidNumber(self.calc_char_pos_at(start))),
}
}
pub fn parse_list_integer(&mut self) -> Result<i32, Error> {
if self.at_end() {
return Err(Error::UnexpectedEndOfStream);
}
let n = self.parse_integer()?;
self.skip_spaces();
self.parse_list_separator();
Ok(n)
}
pub fn parse_number_or_percent(&mut self) -> Result<f64, Error> {
self.skip_spaces();
let n = self.parse_number()?;
if self.starts_with(b"%") {
self.advance(1);
Ok(n / 100.0)
} else {
Ok(n)
}
}
pub fn parse_list_number_or_percent(&mut self) -> Result<f64, Error> {
if self.at_end() {
return Err(Error::UnexpectedEndOfStream);
}
let l = self.parse_number_or_percent()?;
self.skip_spaces();
self.parse_list_separator();
Ok(l)
}
pub fn skip_digits(&mut self) {
self.skip_bytes(|_, c| c.is_digit());
}
#[inline]
pub(crate) fn parse_list_separator(&mut self) {
if self.is_curr_byte_eq(b',') {
self.advance(1);
}
}
}
#[rustfmt::skip]
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_integer_1() {
let mut s = Stream::from("10");
assert_eq!(s.parse_integer().unwrap(), 10);
}
#[test]
fn parse_err_integer_1() {
let mut s = Stream::from("10000000000000");
assert_eq!(s.parse_integer().unwrap_err().to_string(),
"invalid number at position 1");
}
}