Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_reflect/src/path/parse.rs
6599 views
1
use core::{
2
fmt::{self, Write},
3
num::ParseIntError,
4
str::from_utf8_unchecked,
5
};
6
use thiserror::Error;
7
8
use super::{Access, ReflectPathError};
9
10
/// An error that occurs when parsing reflect path strings.
11
#[derive(Debug, PartialEq, Eq, Error)]
12
#[error(transparent)]
13
pub struct ParseError<'a>(Error<'a>);
14
15
/// A parse error for a path string.
16
#[derive(Debug, PartialEq, Eq, Error)]
17
enum 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
37
pub(super) struct PathParser<'a> {
38
path: &'a str,
39
remaining: &'a [u8],
40
}
41
42
impl<'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
// Return with `None` if empty.
52
let (first_byte, remaining) = to_parse.split_first()?;
53
54
if let Some(token) = Token::symbol_from_byte(*first_byte) {
55
self.remaining = remaining; // NOTE: all symbols are ASCII
56
return Some(token);
57
}
58
// We are parsing either `0123` or `field`.
59
// If we do not find a subsequent token, we are at the end of the parse string.
60
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
// SAFETY: This relies on `self.remaining` always remaining valid UTF8:
63
// - self.remaining is a slice derived from self.path (valid &str)
64
// - The slice's end is either the same as the valid &str or
65
// the last byte before an ASCII utf-8 character (ie: it is a char
66
// boundary).
67
// - The slice always starts after a symbol ie: an ASCII character's boundary.
68
#[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
108
impl<'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)]
127
struct Ident<'a>(&'a str);
128
129
impl<'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)]
147
enum Token<'a> {
148
Dot = b'.',
149
Pound = b'#',
150
OpenBracket = b'[',
151
CloseBracket = b']',
152
Ident(Ident<'a>),
153
}
154
155
impl 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
167
impl<'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)]
181
mod 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
}
205
206