diman_unit_system 0.2.0

Internal procedural macros for diman.
Documentation
use quote::quote;
use syn::DeriveInput;

const ALLOWED_TYPES: &[&str] = &["i8", "i32", "i64"];

pub(crate) fn dimension_impl(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let input: DeriveInput = syn::parse(input).unwrap();
    let methods_impl: proc_macro2::TokenStream = dimension_methods_impl(&input).into();
    let output = quote! {
        #[derive(PartialEq, Eq, Clone, Debug)]
        #input

        #methods_impl
    };
    output.into()
}

pub(crate) fn dimension_methods_impl(input: &DeriveInput) -> proc_macro::TokenStream {
    let type_name = &input.ident;
    let (impl_generics, type_generics, where_clause) = &input.generics.split_for_impl();
    let panic_unexpected_type =
        || panic!("Found unexpected field type while deriving diman_dimension methods.");
    let mut field_names = vec![];
    if let syn::Data::Struct(s) = &input.data {
        if let syn::Fields::Named(fields) = &s.fields {
            for f in fields.named.iter() {
                if let syn::Type::Path(ref type_name) = f.ty {
                    if type_name.path.segments.len() != 1 {
                        panic_unexpected_type();
                    } else {
                        let only_segment_in_path = &type_name.path.segments[0];
                        let type_name = only_segment_in_path.ident.to_string();
                        if !ALLOWED_TYPES.contains(&type_name.as_str()) {
                            panic_unexpected_type();
                        }
                    }
                } else {
                    panic_unexpected_type();
                }
                field_names.push(f.ident.as_ref().unwrap().clone());
            }
        }
    }

    let none_gen: proc_macro2::TokenStream = field_names
        .iter()
        .map(|ident| {
            quote! {
                #ident: 0,
            }
        })
        .collect();

    let mul_gen: proc_macro2::TokenStream = field_names
        .iter()
        .map(|ident| {
            quote! {
                #ident: self.#ident + other.#ident,
            }
        })
        .collect();

    let div_gen: proc_macro2::TokenStream = field_names
        .iter()
        .map(|ident| {
            quote! {
                #ident: self.#ident - other.#ident,
            }
        })
        .collect();

    let inv_gen: proc_macro2::TokenStream = field_names
        .iter()
        .map(|ident| {
            quote! {
                #ident: -self.#ident,
            }
        })
        .collect();

    let powi_gen: proc_macro2::TokenStream = field_names
        .iter()
        .map(|ident| {
            quote! {
                #ident: self.#ident * other,
            }
        })
        .collect();

    let sqrt_safety_gen: proc_macro2::TokenStream = field_names.iter().map(|ident| {
        quote! {
            if self.#ident % 2 != 0 {
                panic!("Cannot take square root of quantity with a dimension that is not divisible by 2 in all components.");
            }
        }
    }).collect();

    let sqrt_gen: proc_macro2::TokenStream = field_names
        .iter()
        .map(|ident| {
            quote! {
                #ident: self.#ident / 2,
            }
        })
        .collect();

    let cbrt_safety_gen: proc_macro2::TokenStream = field_names.iter().map(|ident| {
        quote! {
            if self.#ident % 3 != 0 {
                panic!("Cannot take cubic root of quantity with a dimension that is not divisible by 3 in all components.");
            }
        }
    }).collect();

    let cbrt_gen: proc_macro2::TokenStream = field_names
        .iter()
        .map(|ident| {
            quote! {
                #ident: self.#ident / 3,
            }
        })
        .collect();

    let gen = quote! {
        impl #impl_generics #type_name #type_generics #where_clause {
            pub const fn none() -> Self {
                Self {
                    #none_gen
                }
            }

            pub const fn dimension_mul(self, other: Self) -> Self {
                Self {
                    #mul_gen
                }
            }

            pub const fn dimension_div(self, other: Self) -> Self {
                Self {
                    #div_gen
                }
            }

            pub const fn dimension_inv(self) -> Self {
                Self {
                    #inv_gen
                }
            }

            pub const fn dimension_powi(self, other: i32) -> Self {
                Self {
                    #powi_gen
                }
            }

            pub const fn dimension_sqrt(self) -> Self {
                #sqrt_safety_gen
                Self {
                    #sqrt_gen
                }
            }

            pub const fn dimension_cbrt(self) -> Self {
                #cbrt_safety_gen
                Self {
                    #cbrt_gen
                }
            }
        }
    };
    gen.into()
}