#binary-file #binary #type #cast #binary-data #byte #endian

binary_type_cast

一个使用 TypeCast 宏简化将二进制文件数据解析为各种 Rust 数据类型的 Rust 包。

1 个不稳定版本

0.1.2 2023 年 5 月 3 日
0.1.1 2023 年 4 月 7 日
0.1.0 2023 年 4 月 7 日

#2974 in 解析器实现

每月 21 次下载

MIT/Apache

28KB
297

binary_type_cast

一个使用 TypeCast 宏简化将二进制文件数据解析为各种 Rust 数据类型的 Rust 包。

正在积极开发中!

待办事项

  • 创建测试
  • 更好的错误处理

概述

该宏旨在通过使用描述文件中的文本简化在二进制文件中解析数据记录的过程,例如,一个 desc.xml 文件。以下演示了如何定义一个名为 DataTypes 的自定义枚举并使用 TypeCast 宏。

用法

定义自定义枚举

在此示例中定义 DataTypes,并为每个变体使用 cast 属性。

#[derive(Clone, Copy, Debug, Serialize, Deserialize, TypeCast)]
pub enum DataTypes {
    #[cast(from_le_bytes => f32)]
    AnyCustomVariant,
    #[cast(from_le_bytes => [f32;2])]
    AnyCustomVariant2

}

这将自动生成一个 DataTypesCast 枚举

#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum DataTypesCast {
    AnyCustomVariant(f32),
    AnyCustomVariant2([f32;2]),
}

注意

任何使用 TypeCast 的枚举将简单地在其名称后附加 Cast。这将生成以下内容,编译时硬编码

#[derive(TypeCast)]
enum ExampleEnum {
    //
}

以下内容

#[derive(Clone, Debug, Serialize, Deserialize)]
enum ExampleEnumCast {
    //
}

属性宏 #[derive(Clone, Debug, Serialize, Deserialize)] 目前硬编码在 enum {}Cast 之上。如果有大量的需求,宏可以被修改以只包含在父枚举上定义的项。


代码生成

好吧,为什么不直接定义 DataTypesCast 或 ExampleEnumCast 并跳过属性垃圾?

因为以下内容将自动生成

impl DataTypes {
    pub fn parse(self, input: &mut &[u8]) -> DataTypesCast {
        match self {
            DataTypes::AnyCustomVariant => {
                DataTypesCast::AnyCustomVariant({
                    let (bytes, _) = input.split_at(std::mem::size_of::<f32>());
                    <f32>::from_le_bytes(bytes.try_into().unwrap())
                })
            }
            // This can be generated for any sized array supported by the standard library
            DataTypes::AnyCustomVariant2 => {
                DataTypesCast::AnyCustomVariant2({
                    let mut tmp_vec = std::vec::Vec::new();
                    for _ in 0..2 {
                        let (bytes, rest) = input
                            .split_at(std::mem::size_of::<f32>());
                        let converted = <f32>::from_le_bytes(
                            bytes.try_into().unwrap(),
                        );
                        *input = rest;
                        tmp_vec.push(converted);
                    }
                    let out: [f32; 2] = tmp_vec
                        .into_iter()
                        .collect::<Vec<f32>>()
                        .try_into()
                        .unwrap();
                    out
                })
            }
        }
    }

    // The TryInto implementations are so the values can be used outside of match statements. Right now, its tedious to use, but it works. See the hashmapped_fields example for usage.
    impl std::convert::TryInto<f32> for DataTypesCast {
        type Error = String;
        fn try_into(self) -> Result<f32, Self::Error> {
            match self {
                DataTypesCast::AnyCustomVariant(val) => Ok(val),
                _ => {
                    Err({
                        let res = ::alloc::fmt::format(
                            format_args!(
                                "Cannot convert non-compatible DataTypesCast into {0}",
                                "f32"
                            ),
                        );
                        res
                    })
                }
            }
        }
    }
    // allows 
    impl std::convert::TryInto<[f32; 2]> for DataTypesCast {
        type Error = String;
        fn try_into(self) -> Result<[f32; 2], Self::Error> {
            match self {
                DataTypesCast::AnyCustomVariant2(val) => Ok(val),
                _ => {
                    Err({
                        let res = ::alloc::fmt::format(
                            format_args!(
                                "Cannot convert non-compatible DataTypesCast into {0}",
                                "[f32 ; 2]"
                            ),
                        );
                        res
                    })
                }
            }
        }
    }
}

// Generated at compile time for FromStr trait:
impl std::str::FromStr for DataTypes {
    type Err = Box<dyn std::error::Error + Send + Sync>;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "AnyCustomVariant" => Ok(DataTypes::AnyCustomVariant),
            "AnyCustomVariant2" => Ok(DataTypes::AnyCustomVariant2),
            _ => Err("Invalid variant".into()),
        }
    }
}

如果父枚举有大量的变体,这将非常繁琐地输入。

下一步

定义你的数据结构和函数以解析二进制数据。

在hashmapped_fields示例中,DataRecord是一个包含字段名称和解析值的HashMap的struct。 RecordDescs是一个从desc.xml反序列化描述信息的struct,其描述信息随后用于解析data.dat文件到DataRecord。示例打印出解析的数据在DataTypesCast变体中,然后提取并打印出从匹配语句输出的值,并使用try_into单独输出。

依赖关系

~3MB
~68K SLoC