use quote::quote;
pub struct MacroConf {
pub name: &'static str,
pub builder_path: &'static str,
pub has_generics: bool,
pub variant_enum: Option<&'static str>,
pub methods: &'static [(&'static str, &'static str, bool, bool)],
}
pub const BUILDER_CONFIGS: &[MacroConf] = &[MacroConf {
name: "compose",
builder_path: "crate::builder::FrameBuilder",
has_generics: true,
variant_enum: Some("crate::Version"),
methods: &[
("id", "with_id", false, true),
("order", "with_order", false, true),
("message", "with_message", false, true),
("message_integrity", "with_message_hasher", false, false),
("frame_integrity", "with_witness_hasher", true, false),
("confidentiality", "with_aead", false, false),
("encryptor", "with_encryptor", false, false),
("nonrepudiation", "with_signer", false, false),
("compactness", "with_compression", false, false),
("priority", "with_priority", false, false),
("lifetime", "with_lifetime", false, false),
("previous_frame", "with_previous_hash", false, false),
("matrix", "with_matrix", false, false),
],
}];
pub fn generate_builder_macro(config: &MacroConf) -> proc_macro2::TokenStream {
let macro_name = syn::Ident::new(config.name, proc_macro2::Span::call_site());
let helper_name = syn::Ident::new(&format!("__{}_call", config.name), proc_macro2::Span::call_site());
let builder_rest = config.builder_path.strip_prefix("crate::").unwrap_or(config.builder_path);
let builder_rest_path: syn::Path = match syn::parse_str(builder_rest) {
Ok(path) => path,
Err(err) => return err.to_compile_error(),
};
let builder_path_tokens = quote! { $crate::#builder_rest_path };
let variant_path_tokens = if let Some(variant_enum) = config.variant_enum {
let variant_rest = variant_enum.strip_prefix("crate::").unwrap_or(variant_enum);
let variant_rest_path: syn::Path = match syn::parse_str(variant_rest) {
Ok(path) => path,
Err(err) => return err.to_compile_error(),
};
Some(quote! { $crate::#variant_rest_path })
} else {
None
};
let mut helper_arms = Vec::new();
for (key, method, is_generic_only, _is_required) in config.methods {
let key_ident = syn::Ident::new(key, proc_macro2::Span::call_site());
let method_ident = syn::Ident::new(method, proc_macro2::Span::call_site());
if *is_generic_only {
helper_arms.push(quote! {
($builder:ident; #key_ident : type $ty:ty) => {
$builder = $builder.#method_ident::<$ty>();
};
});
} else {
helper_arms.push(quote! {
($builder:ident; #key_ident < $($g:ty),+ > : $value:expr) => {
$builder = $builder.#method_ident::<$($g),+>($value);
};
});
helper_arms.push(quote! {
($builder:ident; #key_ident : $value:expr) => {
$builder = $builder.#method_ident($value);
};
});
}
}
let builder_type = if config.has_generics {
quote! { #builder_path_tokens<_> }
} else {
quote! { #builder_path_tokens }
};
let helper_macro = quote! {
#[doc(hidden)]
#[macro_export]
macro_rules! #helper_name {
#(#helper_arms)*
($builder:ident; $key:ident $($rest:tt)*) => {
compile_error!(concat!("unknown builder key: ", stringify!($key)));
};
}
};
let common_call_patterns = quote! {
(@call $builder:ident; $key:ident : type $ty:ty) => {
$crate::#helper_name!($builder; $key : type $ty);
};
(@call $builder:ident; $key:ident < $($g:ty),+ > : $value:expr) => {
$crate::#helper_name!($builder; $key<$($g),+> : $value);
};
(@call $builder:ident; $key:ident : $value:expr) => {
$crate::#helper_name!($builder; $key : $value);
};
(@entries $builder:ident; $key:ident : type $ty:ty $(, $($rest:tt)*)?) => {
$crate::#macro_name!(@call $builder; $key : type $ty);
$($crate::#macro_name!(@entries $builder; $($rest)*);)?
};
(@entries $builder:ident; $key:ident < $($g:ty),+ > : $value:expr $(, $($rest:tt)*)?) => {
$crate::#macro_name!(@call $builder; $key<$($g),+> : $value);
$($crate::#macro_name!(@entries $builder; $($rest)*);)?
};
(@entries $builder:ident; $key:ident : $value:expr $(, $($rest:tt)*)?) => {
$crate::#macro_name!(@call $builder; $key : $value);
$($crate::#macro_name!(@entries $builder; $($rest)*);)?
};
(@entries $builder:ident;) => {};
};
let main_macro = if let Some(variant) = &variant_path_tokens {
quote! {
#[macro_export]
macro_rules! #macro_name {
#common_call_patterns
($variant_id:ident : $($rest:tt)*) => {{
use $crate::builder::TypeBuilder as _;
let mut __b: #builder_type = ::core::convert::Into::into(#variant::$variant_id);
$crate::#macro_name!(@entries __b; $($rest)*);
__b.build()
}};
}
}
} else {
quote! {
#[macro_export]
macro_rules! #macro_name {
#common_call_patterns
($($rest:tt)*) => {{
use $crate::builder::TypeBuilder as _;
let mut __b: #builder_type = ::core::default::Default::default();
$crate::#macro_name!(@entries __b; $($rest)*);
__b.build()
}};
}
}
};
quote! {
#helper_macro
#main_macro
}
}