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}
41impl<'a> PathParser<'a> {
42 pub(super) fn new(path: &'a str) -> Self {
43 let remaining = path.as_bytes();
44 PathParser { path, remaining }
45 }
46
47 fn next_token(&mut self) -> Option<Token<'a>> {
48 let to_parse = self.remaining;
49
50 let (first_byte, remaining) = to_parse.split_first()?;
52
53 if let Some(token) = Token::symbol_from_byte(*first_byte) {
54 self.remaining = remaining; return Some(token);
56 }
57 let ident_len = to_parse.iter().position(|t| Token::SYMBOLS.contains(t));
60 let (ident, remaining) = to_parse.split_at(ident_len.unwrap_or(to_parse.len()));
61 #[expect(
68 unsafe_code,
69 reason = "We have fulfilled the Safety requirements for `from_utf8_unchecked`."
70 )]
71 let ident = unsafe { from_utf8_unchecked(ident) };
72
73 self.remaining = remaining;
74 Some(Token::Ident(Ident(ident)))
75 }
76
77 fn next_ident(&mut self) -> Result<Ident<'a>, Error<'a>> {
78 match self.next_token() {
79 Some(Token::Ident(ident)) => Ok(ident),
80 Some(other) => Err(Error::ExpectedIdent(other)),
81 None => Err(Error::NoIdent),
82 }
83 }
84
85 fn access_following(&mut self, token: Token<'a>) -> Result<Access<'a>, Error<'a>> {
86 match token {
87 Token::Dot => Ok(self.next_ident()?.field()),
88 Token::Pound => self.next_ident()?.field_index(),
89 Token::Ident(ident) => Ok(ident.field()),
90 Token::CloseBracket => Err(Error::CloseBeforeOpen),
91 Token::OpenBracket => {
92 let index_ident = self.next_ident()?.list_index()?;
93 match self.next_token() {
94 Some(Token::CloseBracket) => Ok(index_ident),
95 Some(other) => Err(Error::BadClose(other)),
96 None => Err(Error::Unclosed),
97 }
98 }
99 }
100 }
101
102 fn offset(&self) -> usize {
103 self.path.len() - self.remaining.len()
104 }
105}
106impl<'a> Iterator for PathParser<'a> {
107 type Item = (Result<Access<'a>, ReflectPathError<'a>>, usize);
108
109 fn next(&mut self) -> Option<Self::Item> {
110 let token = self.next_token()?;
111 let offset = self.offset();
112 Some((
113 self.access_following(token)
114 .map_err(|error| ReflectPathError::ParseError {
115 offset,
116 path: self.path,
117 error: ParseError(error),
118 }),
119 offset,
120 ))
121 }
122}
123
124#[derive(Debug, PartialEq, Eq)]
125struct Ident<'a>(&'a str);
126
127impl<'a> Ident<'a> {
128 fn field(self) -> Access<'a> {
129 let field = |_| Access::Field(self.0.into());
130 self.0.parse().map(Access::TupleIndex).unwrap_or_else(field)
131 }
132 fn field_index(self) -> Result<Access<'a>, Error<'a>> {
133 Ok(Access::FieldIndex(self.0.parse()?))
134 }
135 fn list_index(self) -> Result<Access<'a>, Error<'a>> {
136 Ok(Access::ListIndex(self.0.parse()?))
137 }
138}
139
140#[derive(Debug, PartialEq, Eq)]
144#[repr(u8)]
145enum Token<'a> {
146 Dot = b'.',
147 Pound = b'#',
148 OpenBracket = b'[',
149 CloseBracket = b']',
150 Ident(Ident<'a>),
151}
152impl fmt::Display for Token<'_> {
153 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154 match self {
155 Token::Dot => f.write_char('.'),
156 Token::Pound => f.write_char('#'),
157 Token::OpenBracket => f.write_char('['),
158 Token::CloseBracket => f.write_char(']'),
159 Token::Ident(ident) => f.write_str(ident.0),
160 }
161 }
162}
163impl<'a> Token<'a> {
164 const SYMBOLS: &'static [u8] = b".#[]";
165 fn symbol_from_byte(byte: u8) -> Option<Self> {
166 match byte {
167 b'.' => Some(Self::Dot),
168 b'#' => Some(Self::Pound),
169 b'[' => Some(Self::OpenBracket),
170 b']' => Some(Self::CloseBracket),
171 _ => None,
172 }
173 }
174}
175
176#[cfg(test)]
177mod test {
178 use super::*;
179 use crate::path::ParsedPath;
180
181 #[test]
182 fn parse_invalid() {
183 assert_eq!(
184 ParsedPath::parse_static("x.."),
185 Err(ReflectPathError::ParseError {
186 error: ParseError(Error::ExpectedIdent(Token::Dot)),
187 offset: 2,
188 path: "x..",
189 }),
190 );
191 assert!(matches!(
192 ParsedPath::parse_static("y[badindex]"),
193 Err(ReflectPathError::ParseError {
194 error: ParseError(Error::InvalidIndex(_)),
195 offset: 2,
196 path: "y[badindex]",
197 }),
198 ));
199 }
200}