initial commit
This commit is contained in:
commit
86f9333aec
21 changed files with 6449 additions and 0 deletions
10
macros/src/lib.rs
Normal file
10
macros/src/lib.rs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
mod log_message;
|
||||
|
||||
#[proc_macro_derive(LogMessage, attributes(log))]
|
||||
pub fn derive_log_message(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let ast = syn::parse(input).unwrap();
|
||||
|
||||
log_message::impl_log_message(&ast)
|
||||
.unwrap_or_else(syn::Error::into_compile_error)
|
||||
.into()
|
||||
}
|
||||
114
macros/src/log_message.rs
Normal file
114
macros/src/log_message.rs
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{spanned::Spanned, Data, DeriveInput, Fields};
|
||||
|
||||
fn camel_to_snake_case(input: String) -> String {
|
||||
let mut prev_lowercase = false;
|
||||
let mut output = String::with_capacity(input.len());
|
||||
|
||||
for char in input.chars() {
|
||||
if char.is_lowercase() {
|
||||
prev_lowercase = true;
|
||||
output.push(char);
|
||||
} else {
|
||||
if prev_lowercase {
|
||||
output.push('_');
|
||||
}
|
||||
prev_lowercase = false;
|
||||
output.push(char.to_lowercase().next().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
enum LogOption {
|
||||
Default,
|
||||
Display,
|
||||
Debug,
|
||||
Player,
|
||||
Weapon,
|
||||
}
|
||||
|
||||
pub(crate) fn impl_log_message(ast: &DeriveInput) -> syn::Result<TokenStream> {
|
||||
let Data::Struct(r#struct) = &ast.data else {
|
||||
return Err(syn::Error::new(
|
||||
ast.ident.span(),
|
||||
"LogMessage can only be derived for structs",
|
||||
));
|
||||
};
|
||||
|
||||
let Fields::Named(named_fields) = &r#struct.fields else {
|
||||
return Err(syn::Error::new(
|
||||
ast.span(),
|
||||
"LogMessage cannot be derived for tuple structs",
|
||||
));
|
||||
};
|
||||
|
||||
let mut fields = Vec::with_capacity(named_fields.named.len());
|
||||
for field in &named_fields.named {
|
||||
let mut option = None;
|
||||
for attr in &field.attrs {
|
||||
if attr.path().is_ident("log") {
|
||||
attr.parse_nested_meta(|meta| {
|
||||
if meta.path.is_ident("display") {
|
||||
option = Some(LogOption::Display);
|
||||
Ok(())
|
||||
} else if meta.path.is_ident("debug") {
|
||||
option = Some(LogOption::Debug);
|
||||
Ok(())
|
||||
} else if meta.path.is_ident("player") {
|
||||
option = Some(LogOption::Player);
|
||||
Ok(())
|
||||
} else if meta.path.is_ident("weapon") {
|
||||
option = Some(LogOption::Weapon);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(meta.error("Unrecognised attribute"))
|
||||
}
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
||||
let option = option.unwrap_or(LogOption::Default);
|
||||
|
||||
let field_name = field.ident.as_ref().unwrap();
|
||||
let name = field_name.to_string();
|
||||
|
||||
let getter = match option {
|
||||
LogOption::Default => quote! {
|
||||
(#name, crate::log::LogValue::from(self.#field_name.clone()))
|
||||
},
|
||||
LogOption::Display => quote! {
|
||||
(#name, crate::log::LogValue::Display(&self.#field_name))
|
||||
},
|
||||
LogOption::Debug => quote! {
|
||||
(#name, crate::log::LogValue::Debug(&self.#field_name))
|
||||
},
|
||||
LogOption::Player => quote! {
|
||||
(#name, crate::log::LogValue::Player(self.#field_name))
|
||||
},
|
||||
LogOption::Weapon => quote! {
|
||||
(#name, crate::log::LogValue::Weapon(self.#field_name))
|
||||
},
|
||||
};
|
||||
|
||||
fields.push(getter);
|
||||
}
|
||||
|
||||
let name = &ast.ident;
|
||||
|
||||
let tag = camel_to_snake_case(name.to_string());
|
||||
|
||||
let gen = quote! {
|
||||
impl crate::log::LogMessage for #name {
|
||||
fn tag(&self) -> &'static str { #tag }
|
||||
|
||||
fn entries<'a>(&'a self) -> Vec<(&'static str, crate::log::LogValue<'a>)> {
|
||||
vec![#(#fields,)*]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(gen)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue