use std::fmt::Display;
#[derive(Copy, Clone)]
#[non_exhaustive]
pub struct ParseError {
remaining: usize,
cause: ParseErrorCause,
}
impl ParseError {
pub fn calculate_position(&self, original: &str) -> Option<usize> {
calculate_position(original, self.remaining)
}
pub fn get_cause(&self) -> ParseErrorCause {
self.cause
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum ParseErrorCause {
Expected(&'static str),
Other(&'static str),
}
impl Display for ParseErrorCause {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ParseErrorCause::Expected(e) => write!(f, "expected {e:?}"),
ParseErrorCause::Other(e) => f.write_str(e),
}
}
}
pub type ParseResult<T> = Result<T, ParseError>;
#[derive(Clone, Copy)]
#[must_use]
pub struct ParserState<'s> {
pub(crate) s: &'s str,
}
impl<'s> ParserState<'s> {
pub fn new(s: &'s str) -> Self {
Self { s }
}
pub fn parse_while(
self,
mut parser: impl FnMut(Self) -> ParseResult<(bool, Self)>,
) -> ParseResult<Self> {
let mut me = self;
loop {
let (proceed, new_me) = parser(me)?;
if !proceed {
return Ok(new_me);
}
me = new_me;
}
}
pub fn accept(self, part: &'static str) -> ParseResult<Self> {
match self.s.strip_prefix(part) {
Some(s) => Ok(Self { s }),
None => Err(self.error(ParseErrorCause::Expected(part))),
}
}
pub fn skip_white(self) -> Self {
let (_, me) = self.take_while(char::is_whitespace);
me
}
pub fn parse_u16(self) -> ParseResult<(u16, Self)> {
let (digits, p) = self.take_while(|c| c.is_ascii_digit());
if let Ok(value) = digits.parse() {
Ok((value, p))
} else {
Err(p.error(ParseErrorCause::Other("Expected 16-bit unsigned integer")))
}
}
pub fn take_while(self, mut pred: impl FnMut(char) -> bool) -> (&'s str, Self) {
let idx = self.s.find(move |c| !pred(c)).unwrap_or(self.s.len());
let new = Self { s: &self.s[idx..] };
(&self.s[..idx], new)
}
pub fn get_remaining(self) -> usize {
self.s.len()
}
pub fn is_at_eof(self) -> bool {
self.s.is_empty()
}
pub fn error(self, cause: ParseErrorCause) -> ParseError {
ParseError {
remaining: self.get_remaining(),
cause,
}
}
pub fn calculate_position(self, original: &str) -> Option<usize> {
calculate_position(original, self.get_remaining())
}
}
fn calculate_position(original: &str, remaining: usize) -> Option<usize> {
let prefix_len = original.len().checked_sub(remaining)?;
let prefix = original.get(..prefix_len)?;
Some(prefix.chars().count() + 1)
}