ot-tools-io-derive 0.7.0

Derive proc macros used in the `ot-tools-io` library crate.
Documentation
/*
SPDX-License-Identifier: GPL-3.0-or-later
Copyright © 2024 Mike Robeson [dijksterhuis]
*/

//! Derive macros for boilerplate trait implementation in the `ot_tools_io`
//! library crate

#[macro_use]
extern crate quote;
#[macro_use]
extern crate syn;

extern crate proc_macro;

use proc_macro::TokenStream;
use std::str::FromStr;
use syn::DeriveInput;

/// Adds `std::error::Error` to a type
#[proc_macro_derive(WithStdError)]
pub fn error_with_std_error(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);

    // get the name of the type we want to implement the trait for
    let name = &input.ident;

    let expanded = quote! {
        impl std::error::Error for #name {}
    };
    TokenStream::from(expanded)
}

/// Adds the default implementation of `ot_tools_io::IsDefault` to a type
#[proc_macro_derive(IsDefaultCheck)]
pub fn check_is_default(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);

    // get the name of the type we want to implement the trait for
    let name = &input.ident;

    let expanded = quote! {
        impl crate::IsDefault for #name {}
    };
    TokenStream::from(expanded)
}

/// Adds the `ot_tools_io::CheckIntegrity` trait to a type -- adding the `check_integrity()` method
/// for running header, file version and checksum validation
#[proc_macro_derive(IntegrityChecks)]
pub fn check_integrity_derive(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);

    // get the name of the type we want to implement the trait for
    let name = &input.ident;

    let expanded = quote! {
        impl crate::CheckFileIntegrity for #name {}
    };
    TokenStream::from(expanded)
}

/// Macro to create a standard `[Self; N]` implementation of `ot_tools_io::DefaultsArray` on a type,
/// i.e. a standard array filled with an inferred volume of defaults instances based on type hints
#[proc_macro_derive(ArrayDefaults)]
pub fn defaults_as_array_derive(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);

    // get the name of the type we want to implement the trait for
    let name = &input.ident;

    let expanded = quote! {
        impl<const N: usize> Defaults<[Self; N]> for #name {
            fn defaults() -> [Self; N] where Self: Default {
                from_fn(|_| Self::default())
            }
        }
    };
    TokenStream::from(expanded)
}

/// Macro to create a standard `Box<serde_big_array::Array<Self, N>>` implementation of
/// `ot_tools_io::DefaultsArray` on a type,
/// i.e. a non-standard array filled with an inferred volume of defaults instances based on type
/// hints
/// Requires an existing implementation of `Defaults<[Self; N]>` on the type.
#[proc_macro_derive(BoxedBigArrayDefaults)]
pub fn defaults_as_array_boxed_derive(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);

    // get the name of the type we want to implement the trait for
    let name = &input.ident;

    let expanded = quote! {
        impl<const N: usize> Defaults<Box<Array<Self, N>>> for #name {
            fn defaults() -> Box<Array<Self, N>> where Self: Defaults<[Self; N]> {
                Box::new(Array(
                    <Self as Defaults<[Self; N]>>::defaults()
                ))
            }
        }
    };
    TokenStream::from(expanded)
}

/// Macro that expands an existing `impl TryFrom<u32> for SettingEnum` to account for all lower
/// resolution unsigned type variants (includes borrow variants too).
///
/// Note this implementation is quite simple and hacky, doing string replacement on the original
/// `TryFrom<u32>` to generate additional trait implementations
///
/// ```rust
/// use ot_tools_io_derive::enum_try_from_u32_to_unsigned_types;
///
/// #[derive(Debug, PartialEq)]
/// enum Something { A = 0, B = 1, C = 2 }
///
/// #[derive(Debug)]
/// struct SomethingError;
/// // .. implement std::fmt::Display and stf::error::Error
/// # use std::fmt;
/// # impl fmt::Display for SomethingError {fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
/// #         f.write_str("something error")
/// #     }
/// # }
/// # impl std::error::Error for SomethingError {}
///
/// #[enum_try_from_u32_to_unsigned_types]
/// impl TryFrom<u32> for Something {
///     type Error = SomethingError;
///     fn try_from(value: u32) -> Result<Self, Self::Error> {
///         match value {
///             0 => Ok(Self::A),
///             1 => Ok(Self::B),
///             2 => Ok(Self::C),
///             _ => Err(SomethingError),
///         }
///     }
/// }
///
/// assert_eq!(Something::try_from(0_u8).unwrap(), Something::A);
/// assert_eq!(Something::try_from(1_u8).unwrap(), Something::B);
/// assert_eq!(Something::try_from(2_u8).unwrap(), Something::C);
/// assert_eq!(Something::try_from(3_u8).unwrap_err().to_string(), "something error".to_string());
/// ```
#[proc_macro_attribute]
pub fn enum_try_from_u32_to_unsigned_types(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let mut s = item.to_string();
    for t in ["&u32", "u16", "&u16", "u8", "&u8"] {
        s += &*item.to_string().replace("u32", t);
        s += "\n";
    }

    TokenStream::from_str(&s).unwrap()
}

/// Same as [`macro@enum_try_from_u32_to_unsigned_types`], but for signed types (32-bit and below).
/// ```rust
/// use ot_tools_io_derive::enum_try_from_i32_to_signed_types;
///
/// #[derive(Debug, PartialEq)]
/// enum Something { A = 0, B = 1, C = 2 }
///
/// #[derive(Debug)]
/// struct SomethingError;
/// // .. implement std::fmt::Display and stf::error::Error
/// # use std::fmt;
/// # impl fmt::Display for SomethingError {fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
/// #         f.write_str("something error")
/// #     }
/// # }
/// # impl std::error::Error for SomethingError {}
///
/// #[enum_try_from_i32_to_signed_types]
/// impl TryFrom<i32> for Something {
///     type Error = SomethingError;
///     fn try_from(value: i32) -> Result<Self, Self::Error> {
///         match value {
///             0 => Ok(Self::A),
///             1 => Ok(Self::B),
///             2 => Ok(Self::C),
///             _ => Err(SomethingError),
///         }
///     }
/// }
///
/// assert_eq!(Something::try_from(0_i8).unwrap(), Something::A);
/// assert_eq!(Something::try_from(1_i8).unwrap(), Something::B);
/// assert_eq!(Something::try_from(2_i8).unwrap(), Something::C);
/// assert_eq!(Something::try_from(3_i8).unwrap_err().to_string(), "something error".to_string());
/// ```
#[proc_macro_attribute]
pub fn enum_try_from_i32_to_signed_types(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let mut s = item.to_string();
    for t in ["&i32", "i16", "&i16", "i8", "&i8"] {
        s += &*item.to_string().replace("i32", t);
        s += "\n";
    }
    TokenStream::from_str(&s).unwrap()
}

/// Adds implementations for `Into<u8>`, `Into<u16>` and `Into<u32>`.
/// ```rust
/// use ot_tools_io_derive::IntoUnsigneds;
///
/// #[derive(IntoUnsigneds)]
/// enum Something { A = 0, B = 1, C = 2 }
///
/// let u_8: u8 = Something::A.into();
/// let u_16: u16 = Something::A.into();
/// let u_32: u32 = Something::A.into();
/// assert_eq!(u_8, 0_u8);
/// assert_eq!(u_16, 0_u16);
/// assert_eq!(u_32, 0_u32);
/// ```
#[proc_macro_derive(IntoUnsigneds)]
pub fn into_enum_unsigned_derive(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);

    // get the name of the type we want to implement the trait for
    let name = &input.ident;

    let expanded = quote! {
        impl Into<u8> for #name { fn into(self) -> u8 { self as u8 } }
        impl Into<u16> for #name { fn into(self) -> u16 { self as u16 } }
        impl Into<u32> for #name { fn into(self) -> u32 { self as u32 } }
    };
    TokenStream::from(expanded)
}

/// Adds implementations for `Into<i8>`, `Into<i16>` and `Into<i32>`.
/// ```rust
/// use ot_tools_io_derive::IntoSigneds;
///
/// #[derive(IntoSigneds)]
/// enum Something { A = 0, B = 1, C = 2 }
///
/// let i_8: i8 = Something::A.into();
/// let i_16: i16 = Something::A.into();
/// let i_32: i32 = Something::A.into();
/// assert_eq!(i_8, 0_i8);
/// assert_eq!(i_16, 0_i16);
/// assert_eq!(i_32, 0_i32);
/// ```
#[proc_macro_derive(IntoSigneds)]
pub fn into_enum_signed_derive(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);

    // get the name of the type we want to implement the trait for
    let name = &input.ident;

    let expanded = quote! {
        impl Into<i8> for #name { fn into(self) -> i8 { self as i8 } }
        impl Into<i16> for #name { fn into(self) -> i16 { self as i16 } }
        impl Into<i32> for #name { fn into(self) -> i32 { self as i32 } }
    };
    TokenStream::from(expanded)
}

/// Adds a basic [`AsRef`] trait implementation on the type.
///
/// Calling `as_ref()` will return a reference to `self`.
#[proc_macro_derive(AsRefDerive)]
pub fn asref_derive(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let name = &input.ident;
    let expanded = quote! {
        impl AsRef<#name> for #name {
            fn as_ref(&self) -> &#name {
                self
            }
        }
    };
    TokenStream::from(expanded)
}

/// Adds a basic [`AsMut`] trait implementation on the type.
///
/// Calling `as_mut()` will return a mutable reference to `self`.
#[proc_macro_derive(AsMutDerive)]
pub fn asmut_derive(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let name = &input.ident;
    let expanded = quote! {
        impl AsMut<#name> for #name {
            fn as_mut(&mut self) -> &mut #name {
                self
            }
        }
    };
    TokenStream::from(expanded)
}

/// Adds a bunch of boilerplate methods for a new array container type.
///
/// **WARNING**: This is fragile and relies on string interpolation of the type name.
#[proc_macro_derive(ContainerArrayMethods)]
pub fn container_type_derive(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);

    // get the name of the type we want to implement the trait for
    let name = &input.ident;

    let non_arr_name = name.to_string().replace("Array", "");
    let into_name = name.to_string() + "IntoIter";

    let expanded = quote! {

        // https://doc.rust-lang.org/std/ops/trait.Index.html#examples
        impl std::ops::Index<usize> for #name {
            type Output = non_arr_name;

            fn index(&self, index: usize) -> &Self::Output {
                &self.0[index]
            }
        }
        impl std::ops::IndexMut<usize> for #name {
            fn index_mut(&mut self, index: usize) -> &mut Self::Output {
                &mut self.0[index]
            }
        }
        impl Default for #name {
            fn default() -> Self {
                Self(non_arr_name::defaults())
            }
        }

        impl #name {
            /// Returns an iterator with borrowed [`non_arr_name`] items
            pub fn iter(&self) -> Iter<'_, non_arr_name> {
                self.0.iter()
            }

            /// Returns an iterator with mutably borrowed [`non_arr_name`] items
            pub fn iter_mut(&mut self) -> IterMut<'_, non_arr_name> {
                self.0.iter_mut()
            }

            /// Returns the length of the underlying array
            pub fn len(&self) -> usize {
                self.0.len()
            }

            /// Is underlying array empty?
            pub fn is_empty(&self) -> bool {
                self.0.is_empty()
            }

            /// returns a reversed clone of the underlying array data
            pub fn reversed(&self) -> Self {
                let mut x = self.0.clone();
                x.reverse();
                Self(x)
            }

            /// reverses the underlying array data in place
            pub fn reverse(&mut self) -> () {
                self.0.reverse()
            }

        }

        impl IntoIterator for #name {
            type Item = non_arr_name;
            #[doc(hidden)]
            type IntoIter = into_name;
            fn into_iter(self) -> Self::IntoIter {
                into_name {
                    non_arr_name: self,
                    index: 0
                }
            }
        }

        #[doc(hidden)]
        pub struct into_name {
            non_arr_name: #name,
            index: usize,
        }

        #[doc(hidden)]
        impl Iterator for into_name {
            type Item = non_arr_name;
            fn next(&mut self) -> Option<Self::Item> {
                if self.index < self.non_arr_name.len() {
                    Some(self.non_arr_name[self.index].clone())
                } else {None}
            }
        }
    };
    TokenStream::from_str(
        &expanded
            .to_string()
            .replace("non_arr_name", &non_arr_name)
            .replace("into_name", &into_name),
    )
    .unwrap()
    // TokenStream::from()
}