use std::fmt::{Debug, Display};
use proc_macro2::{self as pm2, Span};
use quote::{quote, ToTokens};
use syn::{
punctuated::Punctuated,
token::{Comma, Semi},
Visibility,
};
use crate::traits::{ToDocInfo, ToMacroPattern};
#[derive(Clone, Copy, Debug)]
pub enum MacroType {
Function,
Struct,
StructTuple,
}
impl Display for MacroType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let item = match self {
MacroType::Function => "fn@",
MacroType::Struct => "struct@",
MacroType::StructTuple => "struct@",
};
write!(f, "{}", item)
}
}
pub fn generate_func_macro<P: ToMacroPattern + ToDocInfo + Clone + PartialEq + Debug>(
vis: Visibility,
item_path: Option<syn::Path>,
item_ident: syn::Ident,
params: Vec<Vec<P>>,
output: MacroType,
) -> pm2::TokenStream {
let first_ref = params
.first()
.cloned()
.expect("at least one match pattern expected");
let func_path_root = item_path
.clone()
.map(|g| {
if g.is_ident(crate::ROOT_VISIBILITY_IDENT) {
quote! {$#g ::}
} else {
quote! {$crate :: #g ::}
}
})
.unwrap_or_default();
let macro_matches: Punctuated<pm2::TokenStream, Semi> = params
.into_iter()
.map(|p| {
let macro_signature = create_macro_signature(&p);
let func_signature = create_func_call_signature(first_ref.as_slice(), &p);
match output {
MacroType::Function | MacroType::StructTuple => quote! {
(#macro_signature) => {
#func_path_root #item_ident(#func_signature)
}
},
MacroType::Struct => quote! {
(#macro_signature) => {
#func_path_root #item_ident{#func_signature}
}
},
}
})
.collect();
let _macro_mod = syn::Ident::new(
&format!("{}_macros", item_ident.to_token_stream()),
Span::call_site(),
);
let macro_def_attr = match &vis {
Visibility::Public(_) => quote! {#[macro_export]},
Visibility::Restricted(_) | Visibility::Inherited => quote! {},
};
let func_dunder_ident = syn::Ident::new(
&format!(
"__{}{}__",
match &item_path {
Some(p) => format!("{}_", p.to_token_stream()),
None => "".to_string(),
},
item_ident.to_token_stream()
),
Span::call_site(),
);
let item_prefix = output.to_string();
let doc_type_info = first_ref
.iter()
.map(|p| {
let info = p.to_doc_info().to_string();
quote! {#[doc = concat!("- ", #info)]}
})
.collect::<pm2::TokenStream>();
quote! {
#[doc(hidden)]
#[allow(unused_macros)]
#macro_def_attr
macro_rules! #func_dunder_ident (
#macro_matches
);
#[doc(inline)]
#[doc = concat!("[`defamed`] wrapper for [`", #item_prefix, stringify!(#item_ident), "`]")]
#[doc = ""]
#doc_type_info
#vis use #func_dunder_ident as #item_ident;
}
}
fn create_macro_signature<P: ToMacroPattern>(params: &[P]) -> pm2::TokenStream {
let seq: Punctuated<pm2::TokenStream, Comma> =
params.iter().filter_map(|p| p.to_macro_pattern()).collect();
seq.to_token_stream()
}
fn create_func_call_signature<P>(reference: &[P], params: &[P]) -> pm2::TokenStream
where
P: ToMacroPattern + PartialEq + Debug,
{
assert!(
reference.len() <= params.len(),
"ref: {:?}\nparams: {:?}",
reference,
params
);
let mut seq: Punctuated<pm2::TokenStream, Comma> = reference
.iter()
.map(|r| {
let p = params
.iter()
.find(|item| *item == r)
.expect("parameter must exist");
p.to_func_call_pattern()
})
.collect();
match params.len().cmp(&reference.len()) {
std::cmp::Ordering::Less => {
unimplemented!("reference must have at least as many elements as params")
}
std::cmp::Ordering::Equal => (),
std::cmp::Ordering::Greater => {
let additional = params[reference.len()..]
.iter()
.map(|p| p.to_func_call_pattern());
seq.extend(additional);
}
}
seq.to_token_stream()
}