use proc_macro2::{Delimiter, Group, Span, TokenStream}; use quote::{quote, ToTokens, TokenStreamExt}; use syn::{parse_macro_input, Attribute, DeriveInput, Ident}; #[proc_macro_derive(CharacterSheet, attributes(Input, InputExpr, Field, FieldExpr, Seperator, Access, AccessSet))] pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let DeriveInput { ident, data, .. } = parse_macro_input!(input as DeriveInput); let mut output = quote! { impl CharacterSheet for #ident }; // Create inputs impl let mut impls = impl_inputs(&data); impls.extend(impl_fields(&data)); impls.extend(impl_display(&data)); impls.extend(impl_get(&data)); impls.extend(impl_set(&data)); output.append(Group::new(Delimiter::Brace, impls)); output.into() } fn get_type_ident(ty: &syn::Type) -> Option { match ty { syn::Type::Path(p) => { if p.path.is_ident("String") { Some(Ident::new("Text", Span::call_site())) } else if p.path.is_ident("i32") { Some(Ident::new("Number", Span::call_site())) } else if p.path.is_ident("bool") { Some(Ident::new("Bool", Span::call_site())) } else { None } } _ => panic!("Invalid data type"), } } fn get_type_ident_set(ty: &syn::Type) -> Option { match ty { syn::Type::Path(p) => { if p.path.is_ident("String") { Some(Ident::new("as_text", Span::call_site())) } else if p.path.is_ident("i32") { Some(Ident::new("as_num", Span::call_site())) } else if p.path.is_ident("bool") { Some(Ident::new("as_bool", Span::call_site())) } else { None } } _ => panic!("Invalid data type"), } } fn get_access_level(attrs: &Vec, ident: &str) -> Ident { attrs.iter() .find(|a| a.path.is_ident(ident)) .map(|a| a.parse_args().unwrap_or(None)) .flatten() .unwrap_or(Ident::new(&"Owner", Span::call_site())) } fn impl_inputs(data: &syn::Data) -> TokenStream { let mut output = TokenStream::new(); output.extend(quote! { fn inputs(&self, access: AccessLevel) -> Vec<(String, EntryType)> }); let mut items = TokenStream::new(); let mut count = 0_usize; if let syn::Data::Struct(ds) = data { if let syn::Fields::Named(fs) = &ds.fields { for f in &fs.named { let name = f.ident.clone().unwrap(); let attr = f.attrs.iter().find(|a| a.path.is_ident("Input")); if let Some(attr) = attr { let arg: syn::LitStr = attr.parse_args().expect("No arguments supplied for Input attribute, usage: `Input(\"Name\")`"); let exp: Option<&syn::Attribute> = f.attrs.iter().find(|a| a.path.is_ident("InputExpr")); let access = get_access_level(&f.attrs, "AccessSet"); count += 1; if let Some(attr) = exp { let exp: syn::Meta = attr.parse_meta().expect("Failed to parse MetaList!"); if let syn::Meta::List(l) = exp { let from_input = &l.nested[1]; items.extend(quote! { if AccessLevel::#access.has_access(access) { v.push((#arg.to_string(), self.#from_input())); } }); } else { panic!("Failed parsing InputExpr attribute, expected `(&mut self, EntryType), (&self) -> EntryType`"); } } else { let t = get_type_ident(&f.ty).expect(&format!("Invalid type for input: {}", name)); items.extend(quote! { if AccessLevel::#access.has_access(access) { v.push((#arg.to_string(), EntryType::#t(self.#name.clone()))); } }); } } } } } let mut vec = quote! { let mut v = Vec::with_capacity(#count); }; vec.extend(items); vec.extend(quote! { v }); output.append(Group::new(Delimiter::Brace, vec)); output } fn impl_fields(data: &syn::Data) -> TokenStream { let mut output = TokenStream::new(); output.extend(quote! { fn fields(&self) -> Vec<(String, EntryType)> }); let mut items = TokenStream::new(); if let syn::Data::Struct(ds) = data { if let syn::Fields::Named(fs) = &ds.fields { for f in &fs.named { let name = f.ident.clone().unwrap(); let input_attr = f.attrs.iter().find(|a| a.path.is_ident("Input")); if let Some(attr) = input_attr { let arg: syn::LitStr = attr.parse_args().expect("No arguments supplied for Input attribute, usage: `Input(\"Name\")`"); let exp = f.attrs.iter().find(|a| a.path.is_ident("InputExpr")); if let Some(attr) = exp { let exp: syn::Meta = attr.parse_meta().expect("Failed to parse MetaList!"); if let syn::Meta::List(l) = exp { let from_input = &l.nested[1]; items.extend(quote! { (#arg.to_string(), self.#from_input()), }) } else { panic!("Failed parsing InputExpr attribute, expected `(&mut self, EntryType), (&self) -> EntryType`"); } } else { let t = get_type_ident(&f.ty).expect(&format!("Invalid type for input: {}", name)); items.extend(quote! { (#arg.to_string(), EntryType::#t(self.#name.clone())), }); } } let field_attr = f.attrs.iter().find(|a| a.path.is_ident("Field")); if let Some(attr) = field_attr { let t = get_type_ident(&f.ty).expect(&format!("Invalid type for field: {}", name)); let arg: syn::LitStr = attr.parse_args().expect("Invalid arguments for Field attribute"); let field_expr = f.attrs.iter().find(|a| a.path.is_ident("FieldExpr")); if let Some(exp) = field_expr { let exp: syn::Expr = exp.parse_args().expect("Invalid expression"); items.extend(quote! { (#arg.to_string(), EntryType::Number(#exp)), }) } else { // No expression, guess we just push this and nothing special (should prob be an input but mehhh) items.extend(quote! { (#arg.to_string(), EntryType::#t(self.#name.clone())), }); } } } } } let mut vec = quote! { Vec::from }; vec.append(Group::new(Delimiter::Parenthesis, Group::new(Delimiter::Bracket, items).to_token_stream())); output.append(Group::new(Delimiter::Brace, vec)); output } fn impl_get(data: &syn::Data) -> TokenStream { let mut output = TokenStream::new(); output.extend(quote! { fn get(&self, entry: &str, access: AccessLevel) -> Option }); let mut match_hands = TokenStream::new(); if let syn::Data::Struct(ds) = data { if let syn::Fields::Named(fs) = &ds.fields { for f in &fs.named { let name = f.ident.clone().unwrap(); let access = get_access_level(&f.attrs, "Access"); let input_attr = f.attrs.iter().find(|a| a.path.is_ident("Input")); if let Some(attr) = input_attr { let arg: syn::LitStr = attr.parse_args().expect("No arguments supplied for Input attribute, usage: `Input(\"Name\")`"); let exp: Option<&syn::Attribute> = f.attrs.iter().find(|a| a.path.is_ident("InputExpr")); if let Some(attr) = exp { let exp: syn::Meta = attr.parse_meta().expect("Failed to parse MetaList!"); if let syn::Meta::List(l) = exp { let from_input = &l.nested[1]; match_hands.extend(quote! { #arg => if AccessLevel::#access.has_access(access) { Some(self.#from_input()) } else { None }, }); } else { panic!("Failed parsing InputExpr attribute, expected `(&mut self, EntryType), (&self) -> EntryType`"); } } else { let t = get_type_ident(&f.ty).expect(&format!("Invalid type for input: {}", name)); match_hands.extend(quote! { #arg => if AccessLevel::#access.has_access(access) { Some(EntryType::#t(self.#name.clone())) } else { None }, }); } } let field_attr = f.attrs.iter().find(|a| a.path.is_ident("Field")); if let Some(attr) = field_attr { let t = get_type_ident(&f.ty).expect(&format!("Invalid type for field: {}", name)); let arg: syn::LitStr = attr.parse_args().expect("Invalid arguments for Field attribute"); let field_expr = f.attrs.iter().find(|a| a.path.is_ident("FieldExpr")); if let Some(exp) = field_expr { let exp: syn::Expr = exp.parse_args().expect("Invalid expression"); match_hands.extend(quote! { #arg => if AccessLevel::#access.has_access(access) { Some(EntryType::Number(#exp)) } else { None }, }) } else { // No expression, guess we just push this and nothing special (should prob be an input but mehhh) match_hands.extend(quote! { #arg => if AccessLevel::#access.has_access(access) { Some(EntryType::#t(self.#name.clone())) } else { None }, }); } } } } } match_hands.extend(quote! { _ => None, }); let mut vec = quote! { match(entry) }; vec.append(Group::new(Delimiter::Brace, match_hands)); output.append(Group::new(Delimiter::Brace, vec)); output } fn impl_set(data: &syn::Data) -> TokenStream { let mut output = TokenStream::new(); output.extend(quote! { fn set(&mut self, entry: &str, value: EntryType, access: AccessLevel) }); let mut match_hands = TokenStream::new(); if let syn::Data::Struct(ds) = data { if let syn::Fields::Named(fs) = &ds.fields { for f in &fs.named { let name = f.ident.clone().unwrap(); let input_attr = f.attrs.iter().find(|a| a.path.is_ident("Input")); let access = get_access_level(&f.attrs, "AccessSet"); if let Some(attr) = input_attr { let arg: syn::LitStr = attr.parse_args().expect("No arguments supplied for Input attribute, usage: `Input(\"Name\")`"); let exp = f.attrs.iter().find(|a| a.path.is_ident("InputExpr")); if let Some(attr) = exp { let exp: syn::Meta = attr.parse_meta().expect("Failed to parse MetaList!"); if let syn::Meta::List(l) = exp { let to_input = &l.nested[0]; match_hands.extend(quote! { #arg => if AccessLevel::#access.has_access(access) { self.#to_input(value) }, }); } else { panic!("Failed parsing InputExpr attribute, expected `(&mut self, EntryType), (&self) -> EntryType`"); } } else { let t = get_type_ident_set(&f.ty).expect(&format!("Invalid type for input: {}", name)); match_hands.extend(quote! { #arg => if AccessLevel::#access.has_access(access) { self.#name = value.#t() }, }); } } } } } match_hands.extend(quote! { _ => {} }); let mut vec = quote! { match(entry) }; vec.append(Group::new(Delimiter::Brace, match_hands)); output.append(Group::new(Delimiter::Brace, vec)); output } fn impl_display(data: &syn::Data) -> TokenStream { let mut output = TokenStream::new(); output.extend(quote! { fn display(&self, access: AccessLevel) -> Vec> }); let mut items = TokenStream::new(); let mut count = 0_usize; if let syn::Data::Struct(ds) = data { if let syn::Fields::Named(fs) = &ds.fields { for f in &fs.named { let name = f.ident.clone().unwrap(); if f.attrs.iter().any(|a| a.path.is_ident("Seperator")) { items.extend(quote! { v.push(None); }) } let access = get_access_level(&f.attrs, "Access"); count += 1; let input_attr = f.attrs.iter().find(|a| a.path.is_ident("Input")); if let Some(attr) = input_attr { let arg: syn::LitStr = attr.parse_args().expect("No arguments supplied for Input attribute, usage: `Input(\"Name\")`"); let exp = f.attrs.iter().find(|a| a.path.is_ident("InputExpr")); if let Some(attr) = exp { let exp: syn::Meta = attr.parse_meta().expect("Failed to parse MetaList!"); if let syn::Meta::List(l) = exp { let from_input = &l.nested[1]; items.extend(quote! { if AccessLevel::#access.has_access(access) { v.push(Some((#arg.to_string(), self.#from_input()))); } }) } else { panic!("Failed parsing InputExpr attribute, expected `(&mut self, EntryType), (&self) -> EntryType`"); } } else { let t = get_type_ident(&f.ty).expect(&format!("Invalid type for input: {}", name)); items.extend(quote! { if AccessLevel::#access.has_access(access) { Some((#arg.to_string(), EntryType::#t(self.#name.clone()))); } }); } } let field_attr = f.attrs.iter().find(|a| a.path.is_ident("Field")); if let Some(attr) = field_attr { let t = get_type_ident(&f.ty).expect(&format!("Invalid type for field: {}", name)); let arg: syn::LitStr = attr.parse_args().expect("Invalid arguments for Field attribute"); let field_expr = f.attrs.iter().find(|a| a.path.is_ident("FieldExpr")); if let Some(exp) = field_expr { let exp: syn::Expr = exp.parse_args().expect("Invalid expression"); items.extend(quote! { if AccessLevel::#access.has_access(access) { Some((#arg.to_string(), EntryType::Number(#exp))); } }) } else { // No expression, guess we just push this and nothing special (should prob be an input but mehhh) items.extend(quote! { if AccessLevel::#access.has_access(access) { Some((#arg.to_string(), EntryType::#t(self.#name.clone()))); } }); } } } } } let mut vec = quote! { let mut v = Vec::with_capacity(#count); }; vec.extend( items); vec.extend(quote! { v }); output.append(Group::new(Delimiter::Brace, vec)); output }