bevy_reflect/path/
parse.rs1use 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#[derive(Debug, PartialEq, Eq, Error, Display)]
13#[error(ignore)]
14pub struct ParseError<'a>(Error<'a>);
15
16#[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 let (first_byte, remaining) = to_parse.split_first()?;
57
58 if let Some(token) = Token::symbol_from_byte(*first_byte) {
59 self.remaining = remaining; return Some(token);
61 }
62 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 #[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#[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}