use std::{
fmt::{self, Write},
num::ParseIntError,
str::from_utf8_unchecked,
};
use thiserror::Error;
use super::{Access, ReflectPathError};
#[derive(Debug, PartialEq, Eq, Error)]
#[error(transparent)]
pub struct ParseError<'a>(Error<'a>);
#[derive(Debug, PartialEq, Eq, Error)]
enum Error<'a> {
#[error("expected an identifier, but reached end of path string")]
NoIdent,
#[error("expected an identifier, got '{0}' instead")]
ExpectedIdent(Token<'a>),
#[error("failed to parse index as integer")]
InvalidIndex(#[from] ParseIntError),
#[error("a '[' wasn't closed, reached end of path string before finding a ']'")]
Unclosed,
#[error("a '[' wasn't closed properly, got '{0}' instead")]
BadClose(Token<'a>),
#[error("a ']' was found before an opening '['")]
CloseBeforeOpen,
}
pub(super) struct PathParser<'a> {
path: &'a str,
remaining: &'a [u8],
}
impl<'a> PathParser<'a> {
pub(super) fn new(path: &'a str) -> Self {
let remaining = path.as_bytes();
PathParser { path, remaining }
}
fn next_token(&mut self) -> Option<Token<'a>> {
let to_parse = self.remaining;
let (first_byte, remaining) = to_parse.split_first()?;
if let Some(token) = Token::symbol_from_byte(*first_byte) {
self.remaining = remaining; return Some(token);
}
let ident_len = to_parse.iter().position(|t| Token::SYMBOLS.contains(t));
let (ident, remaining) = to_parse.split_at(ident_len.unwrap_or(to_parse.len()));
#[allow(unsafe_code)]
let ident = unsafe { from_utf8_unchecked(ident) };
self.remaining = remaining;
Some(Token::Ident(Ident(ident)))
}
fn next_ident(&mut self) -> Result<Ident<'a>, Error<'a>> {
match self.next_token() {
Some(Token::Ident(ident)) => Ok(ident),
Some(other) => Err(Error::ExpectedIdent(other)),
None => Err(Error::NoIdent),
}
}
fn access_following(&mut self, token: Token<'a>) -> Result<Access<'a>, Error<'a>> {
match token {
Token::Dot => Ok(self.next_ident()?.field()),
Token::Pound => self.next_ident()?.field_index(),
Token::Ident(ident) => Ok(ident.field()),
Token::CloseBracket => Err(Error::CloseBeforeOpen),
Token::OpenBracket => {
let index_ident = self.next_ident()?.list_index()?;
match self.next_token() {
Some(Token::CloseBracket) => Ok(index_ident),
Some(other) => Err(Error::BadClose(other)),
None => Err(Error::Unclosed),
}
}
}
}
fn offset(&self) -> usize {
self.path.len() - self.remaining.len()
}
}
impl<'a> Iterator for PathParser<'a> {
type Item = (Result<Access<'a>, ReflectPathError<'a>>, usize);
fn next(&mut self) -> Option<Self::Item> {
let token = self.next_token()?;
let offset = self.offset();
Some((
self.access_following(token)
.map_err(|error| ReflectPathError::ParseError {
offset,
path: self.path,
error: ParseError(error),
}),
offset,
))
}
}
#[derive(Debug, PartialEq, Eq)]
struct Ident<'a>(&'a str);
impl<'a> Ident<'a> {
fn field(self) -> Access<'a> {
let field = |_| Access::Field(self.0.into());
self.0.parse().map(Access::TupleIndex).unwrap_or_else(field)
}
fn field_index(self) -> Result<Access<'a>, Error<'a>> {
Ok(Access::FieldIndex(self.0.parse()?))
}
fn list_index(self) -> Result<Access<'a>, Error<'a>> {
Ok(Access::ListIndex(self.0.parse()?))
}
}
#[derive(Debug, PartialEq, Eq)]
#[repr(u8)]
enum Token<'a> {
Dot = b'.',
Pound = b'#',
OpenBracket = b'[',
CloseBracket = b']',
Ident(Ident<'a>),
}
impl fmt::Display for Token<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Token::Dot => f.write_char('.'),
Token::Pound => f.write_char('#'),
Token::OpenBracket => f.write_char('['),
Token::CloseBracket => f.write_char(']'),
Token::Ident(ident) => f.write_str(ident.0),
}
}
}
impl<'a> Token<'a> {
const SYMBOLS: &'static [u8] = b".#[]";
fn symbol_from_byte(byte: u8) -> Option<Self> {
match byte {
b'.' => Some(Self::Dot),
b'#' => Some(Self::Pound),
b'[' => Some(Self::OpenBracket),
b']' => Some(Self::CloseBracket),
_ => None,
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::path::ParsedPath;
#[test]
fn parse_invalid() {
assert_eq!(
ParsedPath::parse_static("x.."),
Err(ReflectPathError::ParseError {
error: ParseError(Error::ExpectedIdent(Token::Dot)),
offset: 2,
path: "x..",
}),
);
assert!(matches!(
ParsedPath::parse_static("y[badindex]"),
Err(ReflectPathError::ParseError {
error: ParseError(Error::InvalidIndex(_)),
offset: 2,
path: "y[badindex]",
}),
));
}
}