bevy_reflect/path/
parse.rs1use core::{
2 fmt::{self, Write},
3 num::ParseIntError,
4 str::from_utf8_unchecked,
5};
6use thiserror::Error;
7
8use super::{Access, ReflectPathError};
9
10#[derive(Debug, PartialEq, Eq, Error)]
12#[error(transparent)]
13pub struct ParseError<'a>(Error<'a>);
14
15#[derive(Debug, PartialEq, Eq, Error)]
17enum Error<'a> {
18 #[error("expected an identifier, but reached end of path string")]
19 NoIdent,
20
21 #[error("expected an identifier, got '{0}' instead")]
22 ExpectedIdent(Token<'a>),
23
24 #[error("failed to parse index as integer")]
25 InvalidIndex(#[from] ParseIntError),
26
27 #[error("a '[' wasn't closed, reached end of path string before finding a ']'")]
28 Unclosed,
29
30 #[error("a '[' wasn't closed properly, got '{0}' instead")]
31 BadClose(Token<'a>),
32
33 #[error("a ']' was found before an opening '['")]
34 CloseBeforeOpen,
35}
36
37pub(super) struct PathParser<'a> {
38 path: &'a str,
39 remaining: &'a [u8],
40}
41
42impl<'a> PathParser<'a> {
43 pub(super) fn new(path: &'a str) -> Self {
44 let remaining = path.as_bytes();
45 PathParser { path, remaining }
46 }
47
48 fn next_token(&mut self) -> Option<Token<'a>> {
49 let to_parse = self.remaining;
50
51 let (first_byte, remaining) = to_parse.split_first()?;
53
54 if let Some(token) = Token::symbol_from_byte(*first_byte) {
55 self.remaining = remaining; return Some(token);
57 }
58 let ident_len = to_parse.iter().position(|t| Token::SYMBOLS.contains(t));
61 let (ident, remaining) = to_parse.split_at(ident_len.unwrap_or(to_parse.len()));
62 #[expect(
69 unsafe_code,
70 reason = "We have fulfilled the Safety requirements for `from_utf8_unchecked`."
71 )]
72 let ident = unsafe { from_utf8_unchecked(ident) };
73
74 self.remaining = remaining;
75 Some(Token::Ident(Ident(ident)))
76 }
77
78 fn next_ident(&mut self) -> Result<Ident<'a>, Error<'a>> {
79 match self.next_token() {
80 Some(Token::Ident(ident)) => Ok(ident),
81 Some(other) => Err(Error::ExpectedIdent(other)),
82 None => Err(Error::NoIdent),
83 }
84 }
85
86 fn access_following(&mut self, token: Token<'a>) -> Result<Access<'a>, Error<'a>> {
87 match token {
88 Token::Dot => Ok(self.next_ident()?.field()),
89 Token::Pound => self.next_ident()?.field_index(),
90 Token::Ident(ident) => Ok(ident.field()),
91 Token::CloseBracket => Err(Error::CloseBeforeOpen),
92 Token::OpenBracket => {
93 let index_ident = self.next_ident()?.list_index()?;
94 match self.next_token() {
95 Some(Token::CloseBracket) => Ok(index_ident),
96 Some(other) => Err(Error::BadClose(other)),
97 None => Err(Error::Unclosed),
98 }
99 }
100 }
101 }
102
103 fn offset(&self) -> usize {
104 self.path.len() - self.remaining.len()
105 }
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}
154
155impl fmt::Display for Token<'_> {
156 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157 match self {
158 Token::Dot => f.write_char('.'),
159 Token::Pound => f.write_char('#'),
160 Token::OpenBracket => f.write_char('['),
161 Token::CloseBracket => f.write_char(']'),
162 Token::Ident(ident) => f.write_str(ident.0),
163 }
164 }
165}
166
167impl<'a> Token<'a> {
168 const SYMBOLS: &'static [u8] = b".#[]";
169 fn symbol_from_byte(byte: u8) -> Option<Self> {
170 match byte {
171 b'.' => Some(Self::Dot),
172 b'#' => Some(Self::Pound),
173 b'[' => Some(Self::OpenBracket),
174 b']' => Some(Self::CloseBracket),
175 _ => None,
176 }
177 }
178}
179
180#[cfg(test)]
181mod test {
182 use super::*;
183 use crate::path::ParsedPath;
184
185 #[test]
186 fn parse_invalid() {
187 assert_eq!(
188 ParsedPath::parse_static("x.."),
189 Err(ReflectPathError::ParseError {
190 error: ParseError(Error::ExpectedIdent(Token::Dot)),
191 offset: 2,
192 path: "x..",
193 }),
194 );
195 assert!(matches!(
196 ParsedPath::parse_static("y[badindex]"),
197 Err(ReflectPathError::ParseError {
198 error: ParseError(Error::InvalidIndex(_)),
199 offset: 2,
200 path: "y[badindex]",
201 }),
202 ));
203 }
204}