Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_scene/macros/src/bsn/parse.rs
30638 views
1
use crate::bsn::types::{
2
Bsn, BsnConstructor, BsnEntry, BsnFields, BsnListRoot, BsnNamedField, BsnRelatedSceneList,
3
BsnRoot, BsnScene, BsnSceneFn, BsnSceneFnArg, BsnSceneFnArgs, BsnSceneList, BsnSceneListItem,
4
BsnSceneListItems, BsnTuple, BsnType, BsnUnnamedField, BsnValue,
5
};
6
use bevy_macro_utils::{path_to_string, PathType};
7
use proc_macro2::{Delimiter, TokenStream, TokenTree};
8
use quote::quote;
9
use syn::{
10
braced, bracketed,
11
buffer::Cursor,
12
parenthesized,
13
parse::{Parse, ParseBuffer, ParseStream},
14
spanned::Spanned,
15
token::{At, Brace, Bracket, Colon, Comma, Paren, Tilde},
16
Block, Expr, Ident, Lit, LitStr, Path, Result, Token,
17
};
18
19
/// Functionally identical to [`Punctuated`](syn::punctuated::Punctuated), but fills the given `$list` Vec instead
20
/// of allocating a new one inside [`Punctuated`](syn::punctuated::Punctuated). This exists to avoid allocating an intermediate Vec.
21
///
22
/// This also attempts to parse $parse a second time _before_ parsing $separator, as this enables autocomplete to work in cases where
23
/// it is being typed in the middle of a list
24
macro_rules! parse_punctuated_vec_autocomplete_friendly {
25
($list:ident, $input:ident, $parse:ident, $separator:ident) => {
26
loop {
27
if $input.is_empty() {
28
break;
29
}
30
let value = $input.parse::<$parse>()?;
31
$list.push(value);
32
if $input.is_empty() {
33
break;
34
}
35
36
// Try parsing without a comma separator first. This makes autocomplete
37
// work in more places
38
if !$input.is_empty() && !$input.peek($separator) {
39
let value = $input.parse::<$parse>()?;
40
$list.push(value);
41
}
42
$input.parse::<$separator>()?;
43
}
44
};
45
}
46
47
impl Parse for BsnRoot {
48
fn parse(input: ParseStream) -> Result<Self> {
49
Ok(BsnRoot(input.parse::<Bsn<true>>()?))
50
}
51
}
52
53
impl Parse for BsnListRoot {
54
fn parse(input: ParseStream) -> Result<Self> {
55
Ok(BsnListRoot(input.parse::<BsnSceneListItems>()?))
56
}
57
}
58
59
impl<const ALLOW_FLAT: bool> Parse for Bsn<ALLOW_FLAT> {
60
fn parse(input: ParseStream) -> Result<Self> {
61
let mut entries = Vec::new();
62
let mut found_cached_scene = false;
63
if input.peek(Paren) {
64
let content;
65
parenthesized![content in input];
66
while !content.is_empty() {
67
let entry = BsnEntry::parse(&content, found_cached_scene)?;
68
if matches!(entry, BsnEntry::CachedScene(_)) {
69
found_cached_scene = true;
70
}
71
entries.push(entry);
72
}
73
} else if ALLOW_FLAT {
74
while !input.is_empty() {
75
let entry = BsnEntry::parse(input, found_cached_scene)?;
76
if matches!(entry, BsnEntry::CachedScene(_)) {
77
found_cached_scene = true;
78
}
79
entries.push(entry);
80
if input.peek(Comma) {
81
// Not ideal, but this anticipatory break allows us to parse non-parenthesized
82
// flat Bsn entries in SceneLists
83
break;
84
}
85
}
86
} else {
87
entries.push(BsnEntry::parse(input, found_cached_scene)?);
88
}
89
90
Ok(Self { entries })
91
}
92
}
93
94
impl BsnEntry {
95
fn parse(input: ParseStream, found_cached_scene: bool) -> Result<Self> {
96
Ok(if input.peek(Token![:]) {
97
BsnEntry::CachedScene(BsnScene::parse(input, found_cached_scene)?)
98
} else if input.peek(Token![#]) {
99
input.parse::<Token![#]>()?;
100
if input.peek(Brace) {
101
BsnEntry::NameExpression(braced_tokens(input)?)
102
} else {
103
BsnEntry::Name(input.parse::<Ident>()?)
104
}
105
} else if input.peek(Brace) || input.peek(At) {
106
BsnEntry::UncachedScene(BsnScene::parse(input, found_cached_scene)?)
107
} else {
108
let is_template = input.peek(Tilde);
109
if is_template {
110
input.parse::<Tilde>()?;
111
}
112
let mut path = input.parse::<Path>()?;
113
let path_type = PathType::new(&path);
114
match path_type {
115
PathType::Type | PathType::Enum => {
116
let enum_variant = if matches!(path_type, PathType::Enum) {
117
take_last_path_ident(&mut path)
118
} else {
119
None
120
};
121
if input.peek(Bracket) {
122
// TODO: fail if this is an enum variant
123
BsnEntry::RelatedSceneList(BsnRelatedSceneList {
124
relationship_path: path,
125
scene_list: input.parse::<BsnSceneList>()?,
126
})
127
} else {
128
let fields = input.parse::<BsnFields>()?;
129
let bsn_type = BsnType {
130
path,
131
enum_variant,
132
fields,
133
};
134
if is_template {
135
BsnEntry::TemplatePatch(bsn_type)
136
} else {
137
BsnEntry::FromTemplatePatch(bsn_type)
138
}
139
}
140
}
141
PathType::TypeConst => {
142
let const_ident = take_last_path_ident(&mut path).unwrap();
143
BsnEntry::TemplateConst {
144
type_path: path,
145
const_ident,
146
}
147
}
148
PathType::Const => {
149
return Err(syn::Error::new(
150
path.span(),
151
"Consts are not currently supported in this position",
152
))
153
}
154
PathType::TypeFunction => {
155
let function = take_last_path_ident(&mut path).unwrap();
156
157
let bsn_constructor = BsnConstructor {
158
type_path: path,
159
function,
160
args: input.parse()?,
161
};
162
if is_template {
163
BsnEntry::TemplateConstructor(bsn_constructor)
164
} else {
165
BsnEntry::FromTemplateConstructor(bsn_constructor)
166
}
167
}
168
PathType::Function => {
169
if input.peek(Paren) {
170
let args = input.parse()?;
171
BsnEntry::UncachedScene(BsnScene::Fn(BsnSceneFn { path, args }))
172
} else {
173
BsnEntry::UncachedScene(BsnScene::Expression(quote! {#path}))
174
}
175
}
176
}
177
})
178
}
179
}
180
impl Parse for BsnSceneList {
181
fn parse(input: ParseStream) -> Result<Self> {
182
let content;
183
bracketed!(content in input);
184
Ok(BsnSceneList(content.parse::<BsnSceneListItems>()?))
185
}
186
}
187
188
impl Parse for BsnSceneListItems {
189
fn parse(input: ParseStream) -> Result<Self> {
190
let mut scenes = Vec::new();
191
parse_punctuated_vec_autocomplete_friendly!(scenes, input, BsnSceneListItem, Comma);
192
Ok(BsnSceneListItems(scenes))
193
}
194
}
195
196
impl Parse for BsnSceneListItem {
197
fn parse(input: ParseStream) -> Result<Self> {
198
Ok(if input.peek(Brace) {
199
let block = input.parse::<Block>()?;
200
BsnSceneListItem::Expression(block.stmts)
201
} else {
202
BsnSceneListItem::Scene(input.parse::<Bsn<true>>()?)
203
})
204
}
205
}
206
207
impl Parse for BsnSceneFnArgs {
208
fn parse(input: ParseStream) -> Result<Self> {
209
let args = if input.peek(Paren) {
210
let content;
211
parenthesized!(content in input);
212
Some(content.parse_terminated(BsnSceneFnArg::parse, Token![,])?)
213
} else {
214
None
215
};
216
Ok(Self(args))
217
}
218
}
219
220
impl Parse for BsnSceneFnArg {
221
fn parse(input: ParseStream) -> Result<Self> {
222
if input.peek(Token![#]) {
223
input.parse::<Token![#]>()?;
224
if input.peek(Brace) {
225
Ok(Self::NameExpression(braced_tokens(input)?))
226
} else {
227
Ok(Self::Name(input.parse::<Ident>()?))
228
}
229
} else {
230
Ok(Self::Expr(Expr::parse(input)?))
231
}
232
}
233
}
234
impl BsnScene {
235
fn parse(input: ParseStream, found_cached_scene: bool) -> Result<Self> {
236
let cached = if input.peek(Token![:]) {
237
Some(input.parse::<Token![:]>()?)
238
} else {
239
None
240
};
241
242
let err_if_cached = |msg: &str| {
243
if let Some(colon) = cached {
244
Err(syn::Error::new(colon.span(), msg))
245
} else {
246
Ok(())
247
}
248
};
249
250
if found_cached_scene {
251
err_if_cached("Cannot cache scenes more than once")?;
252
}
253
254
Ok(if input.peek(LitStr) {
255
let path = input.parse::<LitStr>()?;
256
if cached.is_none() {
257
return Err(syn::Error::new(
258
path.span(),
259
"Cannot use scenes from asset path without caching, please add the ':' prefix.",
260
));
261
}
262
BsnScene::Asset(path)
263
} else if input.peek(Brace) {
264
err_if_cached("Cannot cache scene expressions")?;
265
BsnScene::Expression(braced_tokens(input)?)
266
} else if input.peek(At) {
267
input.parse::<At>()?;
268
let sc = input.parse::<BsnType>()?;
269
if sc.fields.len() > 0 {
270
err_if_cached("Cannot cache Scene Components with props/fields")?;
271
}
272
BsnScene::SceneComponent(sc)
273
} else {
274
// PERF: do we really need this fork here?
275
let path = input.fork().parse::<Path>()?;
276
match PathType::new(&path) {
277
PathType::Type | PathType::Enum => {
278
// Scene components are parsed before this if an @ is found.
279
// If this path is hit, that means it wasn't prefixed by @
280
return Err(syn::Error::new(
281
path.span(),
282
format!(
283
"Scene component {} needs to be prefixed by '@'",
284
path_to_string(&path),
285
),
286
));
287
}
288
PathType::Function | PathType::TypeFunction => {
289
let path = input.parse::<Path>()?;
290
let func = BsnSceneFn {
291
path,
292
args: input.parse()?,
293
};
294
if func.args.0.is_some() {
295
err_if_cached("Cannot cache Scene function with arguments")?;
296
}
297
BsnScene::Fn(func)
298
}
299
path_type => {
300
return Err(syn::Error::new(
301
path.span(),
302
format!(
303
"Cannot cache path {} of type {:?}",
304
path_to_string(&path),
305
path_type,
306
),
307
))
308
}
309
}
310
})
311
}
312
}
313
314
impl Parse for BsnType {
315
fn parse(input: ParseStream) -> Result<Self> {
316
let mut path = input.parse::<Path>()?;
317
let enum_variant = match PathType::new(&path) {
318
PathType::Type => None,
319
PathType::Enum => take_last_path_ident(&mut path),
320
PathType::Function | PathType::TypeFunction => {
321
return Err(syn::Error::new(
322
path.span(),
323
"Expected a path to a BSN type but encountered a path to a function.",
324
))
325
}
326
PathType::Const | PathType::TypeConst => {
327
return Err(syn::Error::new(
328
path.span(),
329
"Expected a path to a BSN type but encountered a path to a const.",
330
))
331
}
332
};
333
let fields = input.parse::<BsnFields>()?;
334
Ok(BsnType {
335
path,
336
enum_variant,
337
fields,
338
})
339
}
340
}
341
342
impl Parse for BsnTuple {
343
fn parse(input: ParseStream) -> Result<Self> {
344
let content;
345
parenthesized![content in input];
346
let mut fields = Vec::new();
347
348
while !content.is_empty() {
349
fields.push(content.parse::<BsnValue>()?);
350
}
351
352
Ok(BsnTuple(fields))
353
}
354
}
355
356
impl Parse for BsnFields {
357
fn parse(input: ParseStream) -> Result<Self> {
358
Ok(if input.peek(Brace) {
359
let content;
360
braced![content in input];
361
let mut fields = Vec::new();
362
parse_punctuated_vec_autocomplete_friendly!(fields, content, BsnNamedField, Comma);
363
BsnFields::Named(fields)
364
} else if input.peek(Paren) {
365
let content;
366
parenthesized![content in input];
367
let mut fields = Vec::new();
368
parse_punctuated_vec_autocomplete_friendly!(fields, content, BsnUnnamedField, Comma);
369
BsnFields::Tuple(fields)
370
} else {
371
BsnFields::Named(Vec::new())
372
})
373
}
374
}
375
376
impl Parse for BsnNamedField {
377
fn parse(input: ParseStream) -> Result<Self> {
378
let is_prop = if input.peek(At) {
379
input.parse::<At>()?;
380
true
381
} else {
382
false
383
};
384
let name = input.parse::<Ident>()?;
385
let value = if input.peek(Colon) {
386
input.parse::<Colon>()?;
387
388
if input.is_empty() || input.peek(Comma) {
389
None
390
} else {
391
Some(input.parse::<BsnValue>()?)
392
}
393
} else {
394
None
395
};
396
Ok(BsnNamedField {
397
name,
398
value,
399
is_prop,
400
})
401
}
402
}
403
404
impl Parse for BsnUnnamedField {
405
fn parse(input: ParseStream) -> Result<Self> {
406
let value = input.parse::<BsnValue>()?;
407
Ok(BsnUnnamedField { value })
408
}
409
}
410
411
/// Parse a closure "loosely" without caring about the tokens between `|...|` and `{...}`. This ensures autocomplete works.
412
fn parse_closure_loose(input: &ParseBuffer) -> Result<TokenStream> {
413
let start = input.cursor();
414
input.parse::<Token![|]>()?;
415
let tokens = input.step(|cursor| {
416
let mut rest = *cursor;
417
while let Some((tt, next)) = rest.token_tree() {
418
match &tt {
419
TokenTree::Punct(punct) if punct.as_char() == '|' => {
420
if let Some((TokenTree::Group(group), next)) = next.token_tree()
421
&& group.delimiter() == Delimiter::Brace
422
{
423
return Ok((tokens_between(start, next), next));
424
} else {
425
return Err(cursor.error("closures expect '{' to follow '|'"));
426
}
427
}
428
_ => rest = next,
429
}
430
}
431
Err(cursor.error("no matching `|` was found after this point"))
432
})?;
433
Ok(tokens)
434
}
435
436
// Used to parse a block "loosely" without caring about the content in `{...}`. This ensures autocomplete works.
437
fn braced_tokens(input: &ParseBuffer) -> Result<TokenStream> {
438
let content;
439
braced!(content in input);
440
content.parse::<TokenStream>()
441
}
442
443
// Used to parse parenthesized tokens "loosely" without caring about the content in `(...)`. This ensures autocomplete works.
444
fn parenthesized_tokens(input: &ParseBuffer) -> Result<TokenStream> {
445
let content;
446
parenthesized!(content in input);
447
content.parse::<TokenStream>()
448
}
449
450
fn tokens_between(begin: Cursor, end: Cursor) -> TokenStream {
451
assert!(begin <= end);
452
let mut cursor = begin;
453
let mut tokens = TokenStream::new();
454
while cursor < end {
455
let (token, next) = cursor.token_tree().unwrap();
456
tokens.extend(std::iter::once(token));
457
cursor = next;
458
}
459
tokens
460
}
461
462
impl Parse for BsnValue {
463
fn parse(input: ParseStream) -> Result<Self> {
464
Ok(if input.peek(Brace) {
465
BsnValue::Expr(braced_tokens(input)?)
466
} else if input.peek(Token![const]) && input.peek2(Brace) {
467
let const_token = input.parse::<Token![const]>()?;
468
let braced = braced_tokens(input)?;
469
470
BsnValue::Expr(quote! {#const_token {#braced}})
471
} else if input.peek(Token![unsafe]) && input.peek2(Brace) {
472
let unsafe_token = input.parse::<Token![unsafe]>()?;
473
let braced = braced_tokens(input)?;
474
475
BsnValue::Expr(quote! {#unsafe_token {#braced}})
476
} else if input.peek(Token![|]) {
477
let tokens = parse_closure_loose(input)?;
478
BsnValue::Closure(tokens)
479
} else if input.peek(Ident) {
480
let forked = input.fork();
481
let path = forked.parse::<Path>()?;
482
if path.segments.len() == 1 && (forked.is_empty() || forked.peek(Comma)) {
483
return Ok(BsnValue::Ident(input.parse::<Ident>()?));
484
}
485
match PathType::new(&path) {
486
PathType::TypeFunction | PathType::Function => {
487
input.parse::<Path>()?;
488
let token_stream = parenthesized_tokens(input)?;
489
BsnValue::Expr(quote! { #path(#token_stream) })
490
}
491
PathType::Const | PathType::TypeConst => {
492
input.parse::<Path>()?;
493
BsnValue::Expr(quote! { #path })
494
}
495
PathType::Type | PathType::Enum => BsnValue::Type(input.parse::<BsnType>()?),
496
}
497
} else if input.peek(Lit) {
498
BsnValue::Lit(input.parse::<Lit>()?)
499
} else if input.peek(Paren) {
500
BsnValue::Tuple(input.parse::<BsnTuple>()?)
501
} else if input.peek(Token![#]) {
502
input.parse::<Token![#]>()?;
503
if input.peek(Brace) {
504
BsnValue::NameExpression(braced_tokens(input)?)
505
} else {
506
BsnValue::Name(input.parse::<Ident>()?)
507
}
508
} else {
509
return Err(input.error("Unexpected input: Invalid BsnValue. This does not match any expected BSN value type."));
510
})
511
}
512
}
513
514
fn take_last_path_ident(path: &mut Path) -> Option<Ident> {
515
let ident = path.segments.pop().map(|s| s.into_value().ident);
516
path.segments.pop_punct();
517
ident
518
}
519
520