bevy_reflect/path/
parse.rs

1use core::{
2    fmt::{self, Write},
3    num::ParseIntError,
4    str::from_utf8_unchecked,
5};
6
7use derive_more::derive::{Display, Error, From};
8
9use super::{Access, ReflectPathError};
10
11/// An error that occurs when parsing reflect path strings.
12#[derive(Debug, PartialEq, Eq, Error, Display)]
13#[error(ignore)]
14pub struct ParseError<'a>(Error<'a>);
15
16/// A parse error for a path string.
17#[derive(Debug, PartialEq, Eq, Error, Display, From)]
18enum Error<'a> {
19    #[display("expected an identifier, but reached end of path string")]
20    NoIdent,
21
22    #[display("expected an identifier, got '{_0}' instead")]
23    #[error(ignore)]
24    #[from(ignore)]
25    ExpectedIdent(Token<'a>),
26
27    #[display("failed to parse index as integer")]
28    InvalidIndex(ParseIntError),
29
30    #[display("a '[' wasn't closed, reached end of path string before finding a ']'")]
31    Unclosed,
32
33    #[display("a '[' wasn't closed properly, got '{_0}' instead")]
34    #[error(ignore)]
35    #[from(ignore)]
36    BadClose(Token<'a>),
37
38    #[display("a ']' was found before an opening '['")]
39    CloseBeforeOpen,
40}
41
42pub(super) struct PathParser<'a> {
43    path: &'a str,
44    remaining: &'a [u8],
45}
46impl<'a> PathParser<'a> {
47    pub(super) fn new(path: &'a str) -> Self {
48        let remaining = path.as_bytes();
49        PathParser { path, remaining }
50    }
51
52    fn next_token(&mut self) -> Option<Token<'a>> {
53        let to_parse = self.remaining;
54
55        // Return with `None` if empty.
56        let (first_byte, remaining) = to_parse.split_first()?;
57
58        if let Some(token) = Token::symbol_from_byte(*first_byte) {
59            self.remaining = remaining; // NOTE: all symbols are ASCII
60            return Some(token);
61        }
62        // We are parsing either `0123` or `field`.
63        // If we do not find a subsequent token, we are at the end of the parse string.
64        let ident_len = to_parse.iter().position(|t| Token::SYMBOLS.contains(t));
65        let (ident, remaining) = to_parse.split_at(ident_len.unwrap_or(to_parse.len()));
66        // SAFETY: This relies on `self.remaining` always remaining valid UTF8:
67        // - self.remaining is a slice derived from self.path (valid &str)
68        // - The slice's end is either the same as the valid &str or
69        //   the last byte before an ASCII utf-8 character (ie: it is a char
70        //   boundary).
71        // - The slice always starts after a symbol ie: an ASCII character's boundary.
72        #[allow(unsafe_code)]
73        let ident = unsafe { from_utf8_unchecked(ident) };
74
75        self.remaining = remaining;
76        Some(Token::Ident(Ident(ident)))
77    }
78
79    fn next_ident(&mut self) -> Result<Ident<'a>, Error<'a>> {
80        match self.next_token() {
81            Some(Token::Ident(ident)) => Ok(ident),
82            Some(other) => Err(Error::ExpectedIdent(other)),
83            None => Err(Error::NoIdent),
84        }
85    }
86
87    fn access_following(&mut self, token: Token<'a>) -> Result<Access<'a>, Error<'a>> {
88        match token {
89            Token::Dot => Ok(self.next_ident()?.field()),
90            Token::Pound => self.next_ident()?.field_index(),
91            Token::Ident(ident) => Ok(ident.field()),
92            Token::CloseBracket => Err(Error::CloseBeforeOpen),
93            Token::OpenBracket => {
94                let index_ident = self.next_ident()?.list_index()?;
95                match self.next_token() {
96                    Some(Token::CloseBracket) => Ok(index_ident),
97                    Some(other) => Err(Error::BadClose(other)),
98                    None => Err(Error::Unclosed),
99                }
100            }
101        }
102    }
103
104    fn offset(&self) -> usize {
105        self.path.len() - self.remaining.len()
106    }
107}
108impl<'a> Iterator for PathParser<'a> {
109    type Item = (Result<Access<'a>, ReflectPathError<'a>>, usize);
110
111    fn next(&mut self) -> Option<Self::Item> {
112        let token = self.next_token()?;
113        let offset = self.offset();
114        Some((
115            self.access_following(token)
116                .map_err(|error| ReflectPathError::ParseError {
117                    offset,
118                    path: self.path,
119                    error: ParseError(error),
120                }),
121            offset,
122        ))
123    }
124}
125
126#[derive(Debug, PartialEq, Eq)]
127struct Ident<'a>(&'a str);
128
129impl<'a> Ident<'a> {
130    fn field(self) -> Access<'a> {
131        let field = |_| Access::Field(self.0.into());
132        self.0.parse().map(Access::TupleIndex).unwrap_or_else(field)
133    }
134    fn field_index(self) -> Result<Access<'a>, Error<'a>> {
135        Ok(Access::FieldIndex(self.0.parse()?))
136    }
137    fn list_index(self) -> Result<Access<'a>, Error<'a>> {
138        Ok(Access::ListIndex(self.0.parse()?))
139    }
140}
141
142// NOTE: We use repr(u8) so that the `match byte` in `Token::symbol_from_byte`
143// becomes a "check `byte` is one of SYMBOLS and forward its value" this makes
144// the optimizer happy, and shaves off a few cycles.
145#[derive(Debug, PartialEq, Eq)]
146#[repr(u8)]
147enum Token<'a> {
148    Dot = b'.',
149    Pound = b'#',
150    OpenBracket = b'[',
151    CloseBracket = b']',
152    Ident(Ident<'a>),
153}
154impl fmt::Display for Token<'_> {
155    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
156        match self {
157            Token::Dot => f.write_char('.'),
158            Token::Pound => f.write_char('#'),
159            Token::OpenBracket => f.write_char('['),
160            Token::CloseBracket => f.write_char(']'),
161            Token::Ident(ident) => f.write_str(ident.0),
162        }
163    }
164}
165impl<'a> Token<'a> {
166    const SYMBOLS: &'static [u8] = b".#[]";
167    fn symbol_from_byte(byte: u8) -> Option<Self> {
168        match byte {
169            b'.' => Some(Self::Dot),
170            b'#' => Some(Self::Pound),
171            b'[' => Some(Self::OpenBracket),
172            b']' => Some(Self::CloseBracket),
173            _ => None,
174        }
175    }
176}
177
178#[cfg(test)]
179mod test {
180    use super::*;
181    use crate::path::ParsedPath;
182
183    #[test]
184    fn parse_invalid() {
185        assert_eq!(
186            ParsedPath::parse_static("x.."),
187            Err(ReflectPathError::ParseError {
188                error: ParseError(Error::ExpectedIdent(Token::Dot)),
189                offset: 2,
190                path: "x..",
191            }),
192        );
193        assert!(matches!(
194            ParsedPath::parse_static("y[badindex]"),
195            Err(ReflectPathError::ParseError {
196                error: ParseError(Error::InvalidIndex(_)),
197                offset: 2,
198                path: "y[badindex]",
199            }),
200        ));
201    }
202}