bitstructs_macro 0.1.1

Procedural macro for bitstructs.
Documentation
//! 该库是`bitstructs`的宏库。
//!
//! 提供了`bitstruct`、`bitstruct_cow`、`bitstruct_small`三个宏,用于生成`BitStruct`的实现。
//! 以及`bitstruct_field_enum`宏,用于安全的生成`BitStruct`中字段的枚举类型。
//! 详细用法请参考`bitstructs`的文档。
//!
use proc_macro::TokenStream;

mod codegen;
mod tokens;

/// Copy the byte data directly, the `BitStruct` implementation with ownership.
/// 直接复制字节数据,拥有所有权的`BitStruct`实现。
#[proc_macro]
pub fn bitstruct_owned(input: TokenStream) -> TokenStream {
    let bitstruct = syn::parse_macro_input!(input as tokens::Bitstruct);
    let code = codegen::gen_code_with_owned(bitstruct);
    match code {
        Ok(code) => code.into(),
        Err(e) => e.to_compile_error().into(),
    }
}

/// Use `Cow` to implement `BitStruct`.
/// 使用写时复制的`BitStruct`实现。
#[proc_macro]
pub fn bitstruct_cow(input: TokenStream) -> TokenStream {
    let bitstruct = syn::parse_macro_input!(input as tokens::Bitstruct);
    let code = codegen::gen_code_with_cow(bitstruct);
    match code {
        Ok(code) => code.into(),
        Err(e) => e.to_compile_error().into(),
    }
}

/// Suitable for small data structure `BitStruct` implementation.
/// 适用于小型数据结构的`BitStruct`实现。
#[proc_macro]
pub fn bitstruct_small(input: TokenStream) -> TokenStream {
    let bitstruct = syn::parse_macro_input!(input as tokens::Bitstruct);
    let code = codegen::gen_code_for_small(bitstruct);
    match code {
        Ok(code) => code.into(),
        Err(e) => e.to_compile_error().into(),
    }
}

/// Used to declare the enumeration type of the field.
/// 用于声明字段的枚举类型。
///
/// This attribute should be placed above #[derive(Debug)],
/// otherwise Debug will not recognize the automatically completed variants.
/// 这个属性应该在`#[derive(Debug)]`上方,否则Debug不会识别到自动补全的变体。
#[proc_macro_attribute]
pub fn bitstruct_field_enum(attr: TokenStream, input: TokenStream) -> TokenStream {
    let field_bit_width = syn::parse::<syn::LitInt>(attr).map_err(|_| {
        syn::Error::new(
            proc_macro2::Span::call_site(),
            "bitstruct_field_enum attribute should be used like this: #[bitstruct_field_enum(2)], \
            2 is a literal, meaning for the bit width of the field.",
        )
    });
    let field_bit_width = match field_bit_width {
        Ok(field_bit_width) => field_bit_width,
        Err(e) => return e.to_compile_error().into(),
    };

    let field_bit_width = field_bit_width.base10_parse::<u32>().unwrap();
    let field_enum = syn::parse_macro_input!(input as tokens::BitstructFieldEnumType);
    let code = codegen::gen_code_of_enum_type(&field_enum, field_bit_width, true);
    match code {
        Ok(code) => code.into(),
        Err(e) => e.to_compile_error().into(),
    }
}

/// Used to declare the representation of `BitStruct`'s store_type or bit_numbering.
/// 用于描述`BitStruct`的store_type或bit_numbering.
///
/// 将会被其他宏解析后移除,所以此处不需要实现任何功能。
#[proc_macro_attribute]
pub fn bitstruct_repr(_: TokenStream, _: TokenStream) -> TokenStream {
    syn::Error::new(
        proc_macro2::Span::call_site(),
        "bitstruct_repr is used by other macros, do not use it directly.",
    )
    .to_compile_error()
    .into()
}
const BITSTRUCT_REPR: &str = "bitstruct_repr";

#[cfg(test)]
mod test {
    use quote::quote;

    use crate::codegen;
    use crate::tokens;

    #[test]
    fn test_inline_enum_auto_completion() {
        let incomplete = quote::quote! {
            pub struct Test1 {
                a: 4,
                b: 2 =>
                enum B {
                    Foo = 2,
                    Bar = 3
                },
            }
        };
        let complete = quote::quote! {
            pub struct Test1 {
                a: 4,
                b: 2 =>
                enum B {
                    Foo = 2,
                    Bar = 3,
                    __Reserved0 = 0,
                    __Reserved1 = 1,
                },
            }
        };

        let incomplete_bitstruct = syn::parse2::<tokens::Bitstruct>(incomplete.into()).unwrap();
        let complete_bitstruct = syn::parse2::<tokens::Bitstruct>(complete.into()).unwrap();

        assert_eq!(
            codegen::gen_code_with_owned(incomplete_bitstruct.clone())
                .unwrap()
                .to_string(),
            codegen::gen_code_with_owned(complete_bitstruct.clone())
                .unwrap()
                .to_string()
        );
        assert_eq!(
            codegen::gen_code_with_cow(incomplete_bitstruct.clone())
                .unwrap()
                .to_string(),
            codegen::gen_code_with_cow(complete_bitstruct.clone())
                .unwrap()
                .to_string()
        );
        assert_eq!(
            codegen::gen_code_for_small(incomplete_bitstruct)
                .unwrap()
                .to_string(),
            codegen::gen_code_for_small(complete_bitstruct)
                .unwrap()
                .to_string()
        );
    }

    #[test]
    fn test_noninline_enum_auto_completion() {
        let incomplete = quote! {
            pub enum TwoBitEnum {
                Zero = 0,
                Two = 2,
            }
        };
        let complete = quote! {
            pub enum TwoBitEnum {
                Zero = 0,
                Two = 2,
                __Reserved1 = 1,
                __Reserved3 = 3,
            }
        };
        let incomplete_bitstruct_enum =
            syn::parse2::<tokens::BitstructFieldEnumType>(incomplete.into()).unwrap();
        let complete_bitstruct_enum =
            syn::parse2::<tokens::BitstructFieldEnumType>(complete.into()).unwrap();

        assert_eq!(
            codegen::gen_code_of_enum_type(&incomplete_bitstruct_enum, 2, true)
                .unwrap()
                .to_string(),
            codegen::gen_code_of_enum_type(&complete_bitstruct_enum, 2, true)
                .unwrap()
                .to_string()
        )
    }
}