extern crate proc_macro; use proc_macro::TokenStream; use proc_macro2::{Span, TokenStream as TokenStream2}; use quote::quote; use std::ops::Deref; use syn::parse::{Parse, ParseStream}; use syn::{parse_macro_input, Ident, LitStr, Token}; const NUMBER_TYPES: &[&str] = &[ "u8", "i8", "u16", "i16", "u32", "i32", "u64", "i64", "u128", "i128", "usize", "isize", "f32", "f64", ]; // Define the input structure for the macro struct QuantityInput { struct_name: Ident, symbol: LitStr, } // Implement the parsing for the input structure impl Parse for QuantityInput { fn parse(input: ParseStream) -> syn::Result { let struct_name: Ident = input.parse()?; input.parse::()?; let symbol: LitStr = input.parse()?; Ok(QuantityInput { struct_name, symbol, }) } } /// The following imports must be in scope for this macro to work /// ``` /// use physical::quantity::{Quantity, Value}; /// use derive_more::{Add, AddAssign, Display, Sub, SubAssign}; /// use generate_quantity::quantity_type; /// ``` #[proc_macro] pub fn quantity_type(input: TokenStream) -> TokenStream { // Parse the input tokens into the QuantityInput structure let QuantityInput { struct_name, symbol, } = parse_macro_input!(input as QuantityInput); //----- Value Extension ---------------------------------- let mut val_ext_name = String::new(); let struct_name_str = struct_name.to_string(); let mut struct_name_chars = struct_name_str.chars(); let first = struct_name_chars .next() .expect("Struct name cannot be 0 length"); val_ext_name.push(first.to_ascii_lowercase()); for character in struct_name_chars { if character.is_uppercase() { val_ext_name.push('_'); val_ext_name.push(character.to_ascii_lowercase()); } else { val_ext_name.push(character); } } let val_ext_fn_name = Ident::new(val_ext_name.deref(), struct_name.span()); let val_ext_trait_name = Ident::new(format!("{struct_name_str}Val").deref(), struct_name.span()); //----- Conversion impls ---------------------------------- let mut conversion_impls: Vec = Vec::new(); for ¤t_type in NUMBER_TYPES { // Generate conversion methods for all primitive types except the current_type let conversions = NUMBER_TYPES .iter() .filter(|&&t| t != current_type) .map(|&target_type| { let method_name = Ident::new(&format!("as_{}", target_type), Span::call_site()); let target_type = Ident::new(target_type, Span::call_site()); quote! { /// Directly [as] cast this quantities value while maintaining the type of the quantity. #[inline(always)] pub fn #method_name(self) -> #struct_name<#target_type> { #struct_name(self.0 as #target_type) } } }) .collect::>(); let current_type = Ident::new(current_type, Span::call_site()); // Generate the impl block for the struct with the current_type let expanded = quote! { impl #struct_name<#current_type> { #(#conversions)* } }; conversion_impls.push(expanded); } //----- Output Code ---------------------------------- let expanded = quote! { #[derive(Copy, Clone, PartialEq, PartialOrd, Add, AddAssign, Sub, SubAssign, Display)] #[display(fmt = "{} {}", _0, "Self::symbol()")] #[repr(transparent)] pub struct #struct_name(pub V); impl Quantity for #struct_name { #[inline(always)] fn value(self) -> V { self.0 } #[inline(always)] fn symbol() -> &'static str { #symbol } } pub trait #val_ext_trait_name : Value { /// Create a quantity in the given unit from this [Value] #[inline(always)] fn #val_ext_fn_name(self) -> #struct_name { #struct_name(self) } } impl #val_ext_trait_name for V {} #(#conversion_impls)* }; expanded.into() }