use quote::{quote, ToTokens}; use syn::{parse::Parse, parse::ParseStream}; use crate::function::ExportedFn; use crate::rhai_module::ExportedConst; #[cfg(no_std)] use alloc::vec as new_vec; #[cfg(not(no_std))] use std::vec as new_vec; #[cfg(no_std)] use core::mem; #[cfg(not(no_std))] use std::mem; use std::borrow::Cow; use crate::attrs::{AttrItem, ExportInfo, ExportScope, ExportedParams}; use crate::function::ExportedFnParams; #[derive(Debug, Clone, Eq, PartialEq, Hash, Default)] pub(crate) struct ExportedModParams { pub name: String, skip: bool, pub scope: ExportScope, } impl Parse for ExportedModParams { fn parse(args: ParseStream) -> syn::Result { if args.is_empty() { return Ok(ExportedModParams::default()); } let info = crate::attrs::parse_attr_items(args)?; Self::from_info(info) } } impl ExportedParams for ExportedModParams { fn parse_stream(args: ParseStream) -> syn::Result { Self::parse(args) } fn no_attrs() -> Self { Default::default() } fn from_info(info: ExportInfo) -> syn::Result { let ExportInfo { items: attrs, .. } = info; let mut name = Default::default(); let mut skip = false; let mut scope = None; for attr in attrs { let AttrItem { key, value, .. } = attr; match (key.to_string().as_ref(), value) { ("name", Some(s)) => { let new_name = s.value(); if name == new_name { return Err(syn::Error::new(key.span(), "conflicting name")); } name = new_name; } ("name", None) => return Err(syn::Error::new(key.span(), "requires value")), ("skip", None) => skip = true, ("skip", Some(s)) => return Err(syn::Error::new(s.span(), "extraneous value")), ("export_prefix", Some(_)) | ("export_all", None) if scope.is_some() => { return Err(syn::Error::new(key.span(), "duplicate export scope")); } ("export_prefix", Some(s)) => scope = Some(ExportScope::Prefix(s.value())), ("export_prefix", None) => { return Err(syn::Error::new(key.span(), "requires value")) } ("export_all", None) => scope = Some(ExportScope::All), ("export_all", Some(s)) => { return Err(syn::Error::new(s.span(), "extraneous value")) } (attr, _) => { return Err(syn::Error::new( key.span(), format!("unknown attribute '{}'", attr), )) } } } Ok(ExportedModParams { name, skip, scope: scope.unwrap_or_default(), ..Default::default() }) } } #[derive(Debug)] pub(crate) struct Module { mod_all: syn::ItemMod, fns: Vec, consts: Vec, submodules: Vec, params: ExportedModParams, } impl Module { pub fn set_params(&mut self, params: ExportedModParams) -> syn::Result<()> { self.params = params; Ok(()) } } impl Parse for Module { fn parse(input: ParseStream) -> syn::Result { let mut mod_all: syn::ItemMod = input.parse()?; let fns: Vec<_>; let mut consts: Vec<_> = new_vec![]; let mut submodules: Vec<_> = Vec::new(); if let Some((_, ref mut content)) = mod_all.content { // Gather and parse functions. fns = content .iter_mut() .filter_map(|item| match item { syn::Item::Fn(f) => Some(f), _ => None, }) .try_fold(Vec::new(), |mut vec, itemfn| { // #[cfg] attributes are not allowed on functions crate::attrs::deny_cfg_attr(&itemfn.attrs)?; let params: ExportedFnParams = match crate::attrs::inner_item_attributes(&mut itemfn.attrs, "rhai_fn") { Ok(p) => p, Err(e) => return Err(e), }; syn::parse2::(itemfn.to_token_stream()) .and_then(|mut f| { f.set_params(params)?; Ok(f) }) .map(|f| vec.push(f)) .map(|_| vec) })?; // Gather and parse constants definitions. for item in content.iter() { match item { syn::Item::Const(syn::ItemConst { vis, ref expr, ident, attrs, ty, .. }) => { // #[cfg] attributes are not allowed on const declarations crate::attrs::deny_cfg_attr(&attrs)?; if let syn::Visibility::Public(_) = vis { consts.push((ident.to_string(), ty.clone(), expr.as_ref().clone())); } } _ => {} } } // Gather and parse submodule definitions. // // They are actually removed from the module's body, because they will need // re-generating later when generated code is added. submodules.reserve(content.len() - fns.len() - consts.len()); let mut i = 0; while i < content.len() { if let syn::Item::Mod(_) = &content[i] { let mut itemmod = match content.remove(i) { syn::Item::Mod(m) => m, _ => unreachable!(), }; let params: ExportedModParams = match crate::attrs::inner_item_attributes(&mut itemmod.attrs, "rhai_mod") { Ok(p) => p, Err(e) => return Err(e), }; let module = syn::parse2::(itemmod.to_token_stream()).and_then(|mut m| { m.set_params(params)?; Ok(m) })?; submodules.push(module); } else { i += 1; } } } else { fns = new_vec![]; } Ok(Module { mod_all, fns, consts, submodules, params: ExportedModParams::default(), }) } } #[allow(dead_code)] impl Module { pub fn attrs(&self) -> &Vec { &self.mod_all.attrs } pub fn module_name(&self) -> &syn::Ident { &self.mod_all.ident } pub fn exported_name(&self) -> Cow { if !self.params.name.is_empty() { self.params.name.as_str().into() } else { self.module_name().to_string().into() } } pub fn update_scope(&mut self, parent_scope: &ExportScope) { let keep = match (self.params.skip, parent_scope) { (true, _) => false, (_, ExportScope::PubOnly) => matches!(self.mod_all.vis, syn::Visibility::Public(_)), (_, ExportScope::Prefix(s)) => self.mod_all.ident.to_string().starts_with(s), (_, ExportScope::All) => true, }; self.params.skip = !keep; } pub fn skipped(&self) -> bool { self.params.skip } pub fn generate(self) -> proc_macro2::TokenStream { match self.generate_inner() { Ok(tokens) => tokens, Err(e) => e.to_compile_error(), } } fn generate_inner(self) -> Result { // Check for collisions if the "name" attribute was used on inner functions. crate::rhai_module::check_rename_collisions(&self.fns)?; // Extract the current structure of the module. let Module { mut mod_all, mut fns, consts, mut submodules, params, .. } = self; let mod_name = mod_all.ident.clone(); let (_, orig_content) = mod_all.content.take().unwrap(); let mod_attrs = mem::take(&mut mod_all.attrs); if !params.skip { // Generate new module items. // // This is done before inner module recursive generation, because that is destructive. let mod_gen = crate::rhai_module::generate_body( &mut fns, &consts, &mut submodules, ¶ms.scope, ); // NB: submodules must have their new items for exporting generated in depth-first order // to avoid issues caused by re-parsing them let inner_modules: Vec = submodules.drain(..) .try_fold::, _, Result, syn::Error>>( Vec::new(), |mut acc, m| { acc.push(m.generate_inner()?); Ok(acc) })?; // Regenerate the module with the new content added. Ok(quote! { #(#mod_attrs)* pub mod #mod_name { #(#orig_content)* #(#inner_modules)* #mod_gen } }) } else { // Regenerate the original module as-is. Ok(quote! { #(#mod_attrs)* pub mod #mod_name { #(#orig_content)* } }) } } pub fn name(&self) -> &syn::Ident { &self.mod_all.ident } pub fn consts(&self) -> &[ExportedConst] { &self.consts } pub fn fns(&self) -> &[ExportedFn] { &self.fns } pub fn submodules(&self) -> &[Module] { &self.submodules } pub fn content(&self) -> Option<&Vec> { match self.mod_all { syn::ItemMod { content: Some((_, ref vec)), .. } => Some(vec), _ => None, } } }