use {
proc_macro::TokenStream as OriginalTokenStream,
proc_macro2::TokenStream,
quote::quote,
std::{collections::HashSet, str::FromStr},
syn::{
parse::{Parse, ParseStream},
parse_macro_input,
punctuated::Punctuated,
DeriveInput,
Ident,
Token,
},
};
struct Args {
vars: HashSet<Ident>,
}
impl Parse for Args {
fn parse(input: ParseStream) -> Result<Self, syn::Error> {
let vars = Punctuated::<Ident, Token![,]>::parse_terminated(input)?;
Ok(Args {
vars: vars.into_iter().collect(),
})
}
}
#[derive(PartialEq, Eq)]
enum TypeOfField {
Signal,
Resource,
Props,
}
#[proc_macro_attribute]
pub fn store(_: OriginalTokenStream, item: OriginalTokenStream) -> OriginalTokenStream {
let clone_item = item.clone();
let input = parse_macro_input!(clone_item as DeriveInput);
let struct_name = &input.ident;
let struct_visibility = &input.vis;
let fields = if let syn::Data::Struct(syn::DataStruct { fields, .. }) = &input.data {
fields
} else {
return TokenStream::from(quote! {
compile_error!("Only structs are supported for this macro");
})
.into();
};
let mut modified_fields = fields.clone();
let mut all_idents_types = vec![];
let mut props_idents = vec![];
for field in &mut modified_fields {
if let Some(ident) = &field.ident {
if !ident.to_string().starts_with("_modx_reserved") {
all_idents_types.push((ident.clone(), field.ty.clone(), TypeOfField::Signal));
let field_type = field.ty.clone();
let signal_type = quote! { Signal<#field_type> }.into();
field.ty = parse_macro_input!(signal_type as syn::Type);
} else if ident.to_string().starts_with("_modx_reserved_resource_") {
let new_name = ident.to_string().replace("_modx_reserved_resource_", "");
let new_ident = Ident::new(&new_name, proc_macro2::Span::call_site());
field.ident = Some(new_ident.clone());
all_idents_types.push((new_ident, field.ty.clone(), TypeOfField::Resource));
let field_type = field.ty.clone();
let signal_type = quote! { Resource<#field_type> }.into();
field.ty = parse_macro_input!(signal_type as syn::Type);
} else if ident.to_string().starts_with("_modx_reserved_props_") {
let new_name = ident.to_string().replace("_modx_reserved_props_", "");
let new_ident = Ident::new(&new_name, proc_macro2::Span::call_site());
field.ident = Some(new_ident.clone());
all_idents_types.push((new_ident.clone(), field.ty.clone(), TypeOfField::Props));
let field_type = field.ty.clone();
let signal_type = quote! { Signal<#field_type> }.into();
field.ty = parse_macro_input!(signal_type as syn::Type);
props_idents.push((new_ident.clone(), field_type));
}
}
}
let impl_signal_idents = all_idents_types.iter().map(|(ident, ty, type_of_field)| {
match type_of_field {
TypeOfField::Signal | TypeOfField::Props => {
quote! {
impl #struct_name {
pub fn #ident(&self) -> #ty {
self.#ident.read().clone()
}
}
}
},
_ => quote! {},
}
});
let impl_default = {
let default_values = all_idents_types.iter().map(|(ident, ty, type_of_field)| {
let ty_corrected = quote!(#ty).to_string().replace("<", "::<");
let parsed_type: syn::Type = match syn::parse_str(&ty_corrected) {
Ok(t) => t,
Err(why) => {
return why.to_compile_error().into();
},
};
match type_of_field {
TypeOfField::Signal => quote! {
#ident: use_signal(|| #parsed_type::default()),
},
TypeOfField::Props => quote! {
#ident: use_signal(|| props.#ident),
},
_ => quote! { #ident: use_resource(move || async move { unsafe { std::mem::zeroed() } }), },
}
});
let alter_resources = all_idents_types.iter().map(|(ident, _, type_of_field)| {
if *type_of_field == TypeOfField::Resource {
quote! { default_struct.#ident = use_resource(move || async move { default_struct.#ident().await } ); }
} else {
quote!{}
}
});
if props_idents.len() == 0 {
quote! {
impl #struct_name {
pub fn new() -> Self {
let mut default_struct = #struct_name {
#(#default_values)*
};
#(#alter_resources)*
default_struct
}
}
}
}
else {
let structprops_name = quote!(#struct_name).to_string();
let structprops_name: syn::Type =
match syn::parse_str(&format!("{structprops_name}Props")) {
Ok(t) => t,
Err(why) => {
return why.to_compile_error().into();
},
};
let (structprops_fields, structprops_field_inits): (Vec<_>, Vec<_>) = props_idents
.iter()
.map(|(ident, ty)| (quote! ( #ident: #ty, ), quote! ( #ident, )))
.unzip();
let structprops = quote! {
#[derive(Debug)]
#struct_visibility struct #structprops_name {
#(#structprops_fields)*
}
impl #structprops_name {
pub fn new(#(#structprops_fields)*) -> Self {
Self {
#(#structprops_field_inits)*
}
}
}
};
quote! {
#structprops
impl #struct_name {
pub fn new(props: #structprops_name) -> Self {
let mut default_struct = #struct_name {
#(#default_values)*
};
#(#alter_resources)*
default_struct
}
}
}
}
};
quote! {
#[derive(Copy, Clone)]
#struct_visibility struct #struct_name
#modified_fields
#(#impl_signal_idents)*
#impl_default
}
.into()
}
#[proc_macro_attribute]
pub fn resource(attr: OriginalTokenStream, item: OriginalTokenStream) -> OriginalTokenStream {
let clone_item = item.clone();
let input = parse_macro_input!(clone_item as DeriveInput);
let args = parse_macro_input!(attr as Args);
let resource_fields: Vec<String> = args.vars.iter().map(|e| e.to_string()).collect();
let struct_name = &input.ident;
let struct_visibility = &input.vis;
let fields = if let syn::Data::Struct(syn::DataStruct { fields, .. }) = &input.data {
fields
} else {
return TokenStream::from(quote! {
compile_error!("Only structs are supported for this macro");
})
.into();
};
let mut renamed_fields = fields.clone();
for field in &mut renamed_fields {
if let Some(ident) = &mut field.ident {
let string_ident = ident.to_string();
if resource_fields.contains(&string_ident) {
let new_name = format!("_modx_reserved_resource_{}", ident.to_string());
field.ident = Some(Ident::new(&new_name, proc_macro2::Span::call_site()));
}
}
}
let data = item
.clone()
.into_iter()
.collect::<Vec<proc_macro::TokenTree>>();
let mut proc_macro_attributes = vec![];
let mut i = 0;
while let Some(proc_macro::TokenTree::Punct(punct)) = data.get(i) {
if punct.as_char() == '#' {
if let Some(proc_macro::TokenTree::Group(group)) = data.get(i + 1) {
proc_macro_attributes.push(format!("#{group}"));
i += 2;
}
}
}
let attributes_string = proc_macro_attributes
.iter()
.map(|proc_macro_attribute| {
match OriginalTokenStream::from_str(&proc_macro_attribute.clone()) {
Ok(v) => v.into(),
Err(_why) => {
return TokenStream::from(quote! {
compile_error!("A bad proc_macro_attr was found: {proc_macro_attribute}");
})
.into();
},
}
})
.collect::<Vec<TokenStream>>();
let data = quote! {
#(#attributes_string)*
#struct_visibility struct #struct_name
#renamed_fields
};
data.into()
}
#[proc_macro_attribute]
pub fn props(attr: OriginalTokenStream, item: OriginalTokenStream) -> OriginalTokenStream {
let clone_item = item.clone();
let input = parse_macro_input!(clone_item as DeriveInput);
let args = parse_macro_input!(attr as Args);
let resource_fields: Vec<String> = args.vars.iter().map(|e| e.to_string()).collect();
let struct_name = &input.ident;
let struct_visibility = &input.vis;
let fields = if let syn::Data::Struct(syn::DataStruct { fields, .. }) = &input.data {
fields
} else {
return TokenStream::from(quote! {
compile_error!("Only structs are supported for this macro");
})
.into();
};
let mut renamed_fields = fields.clone();
for field in &mut renamed_fields {
if let Some(ident) = &mut field.ident {
let string_ident = ident.to_string();
if resource_fields.contains(&string_ident) {
let new_name = format!("_modx_reserved_props_{}", ident.to_string());
field.ident = Some(Ident::new(&new_name, proc_macro2::Span::call_site()));
}
}
}
let data = item
.clone()
.into_iter()
.collect::<Vec<proc_macro::TokenTree>>();
let mut proc_macro_attributes = vec![];
let mut i = 0;
while let Some(proc_macro::TokenTree::Punct(punct)) = data.get(i) {
if punct.as_char() == '#' {
if let Some(proc_macro::TokenTree::Group(group)) = data.get(i + 1) {
proc_macro_attributes.push(format!("#{group}"));
i += 2;
}
}
}
let attributes_string = proc_macro_attributes
.iter()
.map(|proc_macro_attribute| {
match OriginalTokenStream::from_str(&proc_macro_attribute.clone()) {
Ok(v) => v.into(),
Err(_why) => {
return TokenStream::from(quote! {
compile_error!("A bad proc_macro_attr was found: {proc_macro_attribute}");
})
.into();
},
}
})
.collect::<Vec<TokenStream>>();
let data = quote! {
#(#attributes_string)*
#struct_visibility struct #struct_name
#renamed_fields
};
data.into()
}