251 lines
7.7 KiB
Rust
251 lines
7.7 KiB
Rust
use convert_case::Casing;
|
|
// Copyright (C) 2025 Emilis Bliūdžius
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as
|
|
// published by the Free Software Foundation, either version 3 of the
|
|
// License, or (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
use quote::{ToTokens, quote};
|
|
use syn::{Fields, FieldsNamed, FieldsUnnamed, Ident, spanned::Spanned};
|
|
#[allow(unused)]
|
|
pub struct RefAndMut {
|
|
name: syn::Ident,
|
|
variants: Vec<(Ident, Fields)>,
|
|
}
|
|
|
|
impl RefAndMut {
|
|
pub fn parse(input: syn::DeriveInput) -> syn::Result<Self> {
|
|
let variants = match input.data {
|
|
syn::Data::Enum(data) => data.variants,
|
|
syn::Data::Struct(_) | syn::Data::Union(_) => {
|
|
return Err(syn::Error::new(
|
|
input.span(),
|
|
"RefAndMut can only be used on enums",
|
|
));
|
|
}
|
|
};
|
|
|
|
Ok(Self {
|
|
name: input.ident,
|
|
variants: variants.into_iter().map(|v| (v.ident, v.fields)).collect(),
|
|
})
|
|
}
|
|
}
|
|
|
|
impl ToTokens for RefAndMut {
|
|
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
|
for (field, value) in self.variants.iter().cloned() {
|
|
match value {
|
|
Fields::Named(named) => named_fields(self.name.clone(), field, named, tokens),
|
|
Fields::Unnamed(unnamed) => {
|
|
unnamed_fields(self.name.clone(), field, unnamed, tokens)
|
|
}
|
|
Fields::Unit => continue,
|
|
}
|
|
}
|
|
tokens.extend(quote! {});
|
|
}
|
|
}
|
|
|
|
fn named_fields(
|
|
struct_name: Ident,
|
|
name: Ident,
|
|
fields: FieldsNamed,
|
|
tokens: &mut proc_macro2::TokenStream,
|
|
) {
|
|
let base_name = name.to_string().to_case(convert_case::Case::Pascal);
|
|
let name_ref = Ident::new(format!("{base_name}Ref").as_str(), name.span());
|
|
let fn_name_ref = Ident::new(
|
|
format!(
|
|
"{}_ref",
|
|
name.to_string().to_case(convert_case::Case::Snake)
|
|
)
|
|
.as_str(),
|
|
name.span(),
|
|
);
|
|
let fn_name_mut = Ident::new(
|
|
format!(
|
|
"{}_mut",
|
|
name.to_string().to_case(convert_case::Case::Snake)
|
|
)
|
|
.as_str(),
|
|
name.span(),
|
|
);
|
|
let name_mut = Ident::new(format!("{base_name}Mut").as_str(), name.span());
|
|
let struct_fields_ref = fields
|
|
.named
|
|
.iter()
|
|
.map(|c| {
|
|
let name = c.ident.as_ref().expect("this is a named field??");
|
|
let ty = &c.ty;
|
|
quote! {
|
|
pub #name: &'a #ty,
|
|
}
|
|
})
|
|
.collect::<Box<[_]>>();
|
|
let struct_fields_mut = fields
|
|
.named
|
|
.iter()
|
|
.map(|c| {
|
|
let name = c.ident.as_ref().expect("this is a named field??");
|
|
let ty = &c.ty;
|
|
quote! {
|
|
pub #name: &'a mut #ty,
|
|
}
|
|
})
|
|
.collect::<Box<[_]>>();
|
|
let fields_idents = fields
|
|
.named
|
|
.iter()
|
|
.map(|c| c.ident.as_ref().expect("this is a named field??"))
|
|
.collect::<Box<[_]>>();
|
|
|
|
let for_character = (struct_name == "Role").then_some(quote!{
|
|
impl crate::character::Character {
|
|
pub fn #fn_name_ref<'a>(&'a self) -> core::result::Result<#name_ref<'a>, crate::error::GameError> {
|
|
let got = self.role_title();
|
|
self.role().#fn_name_ref().ok_or(crate::error::GameError::InvalidRole {
|
|
got,
|
|
expected: RoleTitle::#name,
|
|
})
|
|
}
|
|
pub fn #fn_name_mut<'a>(&'a mut self) -> core::result::Result<#name_mut<'a>, crate::error::GameError> {
|
|
let got = self.role_title();
|
|
self.role_mut().#fn_name_mut().ok_or(crate::error::GameError::InvalidRole {
|
|
got,
|
|
expected: RoleTitle::#name,
|
|
})
|
|
}
|
|
}
|
|
});
|
|
|
|
tokens.extend(quote! {
|
|
pub struct #name_ref<'a> {
|
|
#(#struct_fields_ref)*
|
|
}
|
|
|
|
pub struct #name_mut<'a> {
|
|
#(#struct_fields_mut)*
|
|
}
|
|
|
|
#for_character
|
|
|
|
impl #struct_name {
|
|
pub fn #fn_name_ref<'a>(&'a self) -> Option<#name_ref<'a>> {
|
|
match self {
|
|
Self::#name{ #(#fields_idents),* } => Some(#name_ref {#(#fields_idents),* }),
|
|
_ => None,
|
|
}
|
|
}
|
|
pub fn #fn_name_mut<'a>(&'a mut self) -> Option<#name_mut<'a>> {
|
|
match self {
|
|
Self::#name{ #(#fields_idents),* } => Some(#name_mut {#(#fields_idents),* }),
|
|
_ => None,
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
fn unnamed_fields(
|
|
struct_name: Ident,
|
|
name: Ident,
|
|
fields: FieldsUnnamed,
|
|
tokens: &mut proc_macro2::TokenStream,
|
|
) {
|
|
let base_name = name.to_string().to_case(convert_case::Case::Pascal);
|
|
let name_ref = Ident::new(format!("{base_name}Ref").as_str(), name.span());
|
|
let fn_name_ref = Ident::new(
|
|
format!(
|
|
"{}_ref",
|
|
name.to_string().to_case(convert_case::Case::Snake)
|
|
)
|
|
.as_str(),
|
|
name.span(),
|
|
);
|
|
let fn_name_mut = Ident::new(
|
|
format!(
|
|
"{}_mut",
|
|
name.to_string().to_case(convert_case::Case::Snake)
|
|
)
|
|
.as_str(),
|
|
name.span(),
|
|
);
|
|
let name_mut = Ident::new(format!("{base_name}Mut").as_str(), name.span());
|
|
|
|
let fields_ref = fields
|
|
.unnamed
|
|
.iter()
|
|
.map(|field| {
|
|
let ty = &field.ty;
|
|
quote! {
|
|
pub &'a #ty
|
|
}
|
|
})
|
|
.collect::<Box<[_]>>();
|
|
let fields_mut = fields
|
|
.unnamed
|
|
.iter()
|
|
.map(|field| {
|
|
let ty = &field.ty;
|
|
quote! {
|
|
pub &'a mut #ty
|
|
}
|
|
})
|
|
.collect::<Box<[_]>>();
|
|
let fields_names = fields
|
|
.unnamed
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(idx, field)| Ident::new(format!("field{idx}").as_str(), field.span()))
|
|
.collect::<Box<[_]>>();
|
|
let for_character = (struct_name == "Role").then_some(quote! {
|
|
impl crate::character::Character {
|
|
pub fn #fn_name_ref<'a>(&'a self) -> core::result::Result<#name_ref<'a>, crate::error::GameError> {
|
|
let got = self.role_title();
|
|
self.role().#fn_name_ref().ok_or(crate::error::GameError::InvalidRole {
|
|
got,
|
|
expected: RoleTitle::#name,
|
|
})
|
|
}
|
|
pub fn #fn_name_mut<'a>(&'a mut self) -> core::result::Result<#name_mut<'a>, crate::error::GameError> {
|
|
let got = self.role_title();
|
|
self.role_mut().#fn_name_mut().ok_or(crate::error::GameError::InvalidRole {
|
|
got,
|
|
expected: RoleTitle::#name,
|
|
})
|
|
}
|
|
}
|
|
});
|
|
tokens.extend(quote! {
|
|
pub struct #name_ref<'a>(#(#fields_ref),*);
|
|
|
|
pub struct #name_mut<'a>(#(#fields_mut),*);
|
|
|
|
#for_character
|
|
|
|
impl #struct_name {
|
|
pub fn #fn_name_ref<'a>(&'a self) -> Option<#name_ref<'a>> {
|
|
match self {
|
|
Self::#name(#(#fields_names),*) => Some(#name_ref(#(#fields_names),*)),
|
|
_ => None,
|
|
}
|
|
}
|
|
pub fn #fn_name_mut<'a>(&'a mut self) -> Option<#name_mut<'a>> {
|
|
match self {
|
|
Self::#name(#(#fields_names),*) => Some(#name_mut(#(#fields_names),*)),
|
|
_ => None,
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|