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