476 lines
15 KiB
Rust
476 lines
15 KiB
Rust
//! Proc macros for deriving HDF5 compound type mapping.
|
|
//!
|
|
//! Provides `#[derive(H5Type)]` which generates methods for mapping Rust structs
|
|
//! to HDF5 compound datatypes, including serialization and deserialization.
|
|
|
|
use proc_macro::TokenStream;
|
|
use quote::quote;
|
|
use syn::{Data, DeriveInput, Fields, Type, parse_macro_input};
|
|
|
|
/// Derive macro that generates HDF5 compound type mapping for structs.
|
|
///
|
|
/// Generates three methods:
|
|
/// - `hdf5_datatype()` — returns the HDF5 `Datatype::Compound` descriptor
|
|
/// - `to_bytes(&self)` — serializes the struct to HDF5 compound raw bytes
|
|
/// - `from_bytes(data: &[u8])` — deserializes from HDF5 compound raw bytes
|
|
///
|
|
/// # Supported field types
|
|
/// - `f32`, `f64`
|
|
/// - `i8`, `i16`, `i32`, `i64`
|
|
/// - `u8`, `u16`, `u32`, `u64`
|
|
/// - `bool` (stored as `u8`)
|
|
/// - `[T; N]` fixed-size arrays of any supported numeric type
|
|
#[proc_macro_derive(H5Type)]
|
|
pub fn derive_h5type(input: TokenStream) -> TokenStream {
|
|
let input = parse_macro_input!(input as DeriveInput);
|
|
match impl_h5type(&input) {
|
|
Ok(ts) => ts.into(),
|
|
Err(e) => e.to_compile_error().into(),
|
|
}
|
|
}
|
|
|
|
fn impl_h5type(input: &DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
|
|
let name = &input.ident;
|
|
|
|
let fields = match &input.data {
|
|
Data::Struct(data) => match &data.fields {
|
|
Fields::Named(named) => &named.named,
|
|
_ => {
|
|
return Err(syn::Error::new_spanned(
|
|
name,
|
|
"H5Type can only be derived for structs with named fields",
|
|
));
|
|
}
|
|
},
|
|
_ => {
|
|
return Err(syn::Error::new_spanned(
|
|
name,
|
|
"H5Type can only be derived for structs",
|
|
));
|
|
}
|
|
};
|
|
|
|
let mut datatype_member_stmts = Vec::new();
|
|
let mut serialize_stmts = Vec::new();
|
|
let mut deserialize_stmts = Vec::new();
|
|
let mut field_names = Vec::new();
|
|
let mut size_increments = Vec::new();
|
|
|
|
for field in fields.iter() {
|
|
let field_name = field.ident.as_ref().unwrap();
|
|
let field_name_str = field_name.to_string();
|
|
let ty = &field.ty;
|
|
|
|
let (dt_expr, ser_expr, deser_expr, size_expr) = type_mapping(ty, field_name)?;
|
|
|
|
datatype_member_stmts.push(quote! {
|
|
_members.push(clawhdf5_format::datatype::CompoundMember {
|
|
name: #field_name_str.into(),
|
|
byte_offset: _offset,
|
|
datatype: #dt_expr,
|
|
});
|
|
_offset += #size_expr as u64;
|
|
});
|
|
|
|
size_increments.push(quote! { + (#size_expr as usize) });
|
|
serialize_stmts.push(ser_expr);
|
|
deserialize_stmts.push(deser_expr);
|
|
field_names.push(field_name.clone());
|
|
}
|
|
|
|
let expanded = quote! {
|
|
impl #name {
|
|
/// Returns the HDF5 compound datatype descriptor for this struct.
|
|
pub fn hdf5_datatype() -> clawhdf5_format::datatype::Datatype {
|
|
let mut _offset: u64 = 0;
|
|
let mut _members = Vec::new();
|
|
#(#datatype_member_stmts)*
|
|
clawhdf5_format::datatype::Datatype::Compound {
|
|
size: _offset as u32,
|
|
members: _members,
|
|
}
|
|
}
|
|
|
|
/// Serializes this struct to HDF5 compound raw bytes (little-endian).
|
|
pub fn to_bytes(&self) -> Vec<u8> {
|
|
let mut _buf = Vec::with_capacity(Self::_h5_compound_size());
|
|
#(#serialize_stmts)*
|
|
_buf
|
|
}
|
|
|
|
/// Deserializes from HDF5 compound raw bytes (little-endian).
|
|
///
|
|
/// # Panics
|
|
///
|
|
/// Panics if `_data` is shorter than the compound type size.
|
|
pub fn from_bytes(_data: &[u8]) -> Self {
|
|
let _required = Self::_h5_compound_size();
|
|
assert!(
|
|
_data.len() >= _required,
|
|
"from_bytes: input length {} is less than compound size {}",
|
|
_data.len(),
|
|
_required,
|
|
);
|
|
let mut _pos = 0usize;
|
|
#(#deserialize_stmts)*
|
|
Self {
|
|
#(#field_names),*
|
|
}
|
|
}
|
|
|
|
fn _h5_compound_size() -> usize {
|
|
0usize #(#size_increments)*
|
|
}
|
|
}
|
|
};
|
|
|
|
Ok(expanded)
|
|
}
|
|
|
|
fn type_mapping(
|
|
ty: &Type,
|
|
field_name: &syn::Ident,
|
|
) -> syn::Result<(
|
|
proc_macro2::TokenStream, // datatype expression
|
|
proc_macro2::TokenStream, // serialize expression
|
|
proc_macro2::TokenStream, // deserialize expression
|
|
proc_macro2::TokenStream, // size expression
|
|
)> {
|
|
match ty {
|
|
Type::Path(type_path) => {
|
|
let seg = type_path.path.segments.last().unwrap();
|
|
let type_name = seg.ident.to_string();
|
|
match type_name.as_str() {
|
|
"f64" => Ok(float_mapping(field_name, 8, 64, 52, 11, 52, 1023)),
|
|
"f32" => Ok(float_mapping(field_name, 4, 32, 23, 8, 23, 127)),
|
|
"i8" => Ok(int_mapping(field_name, 1, true)),
|
|
"i16" => Ok(int_mapping(field_name, 2, true)),
|
|
"i32" => Ok(int_mapping(field_name, 4, true)),
|
|
"i64" => Ok(int_mapping(field_name, 8, true)),
|
|
"u8" => Ok(int_mapping(field_name, 1, false)),
|
|
"u16" => Ok(int_mapping(field_name, 2, false)),
|
|
"u32" => Ok(int_mapping(field_name, 4, false)),
|
|
"u64" => Ok(int_mapping(field_name, 8, false)),
|
|
"bool" => Ok(bool_mapping(field_name)),
|
|
_ => Err(syn::Error::new_spanned(
|
|
ty,
|
|
format!("unsupported type `{type_name}` for H5Type derive"),
|
|
)),
|
|
}
|
|
}
|
|
Type::Array(arr) => {
|
|
let elem_ty = &*arr.elem;
|
|
let len_expr = &arr.len;
|
|
array_mapping(field_name, elem_ty, len_expr)
|
|
}
|
|
_ => Err(syn::Error::new_spanned(
|
|
ty,
|
|
"unsupported type for H5Type derive",
|
|
)),
|
|
}
|
|
}
|
|
|
|
fn float_mapping(
|
|
field_name: &syn::Ident,
|
|
size: u32,
|
|
precision: u16,
|
|
mant_loc: u8,
|
|
exp_size: u8,
|
|
mant_size: u8,
|
|
exp_bias: u32,
|
|
) -> (
|
|
proc_macro2::TokenStream,
|
|
proc_macro2::TokenStream,
|
|
proc_macro2::TokenStream,
|
|
proc_macro2::TokenStream,
|
|
) {
|
|
let size_lit = size;
|
|
let precision_lit = precision;
|
|
let exp_size_lit = exp_size;
|
|
let mant_size_lit = mant_size;
|
|
let exp_bias_lit = exp_bias;
|
|
let exp_loc: u8 = mant_loc;
|
|
|
|
let dt = quote! {
|
|
clawhdf5_format::datatype::Datatype::FloatingPoint {
|
|
size: #size_lit,
|
|
byte_order: clawhdf5_format::datatype::DatatypeByteOrder::LittleEndian,
|
|
bit_offset: 0,
|
|
bit_precision: #precision_lit,
|
|
exponent_location: #exp_loc,
|
|
exponent_size: #exp_size_lit,
|
|
mantissa_location: 0,
|
|
mantissa_size: #mant_size_lit,
|
|
exponent_bias: #exp_bias_lit,
|
|
}
|
|
};
|
|
|
|
let ser = quote! {
|
|
_buf.extend_from_slice(&self.#field_name.to_le_bytes());
|
|
};
|
|
|
|
let deser = if size == 8 {
|
|
quote! {
|
|
let #field_name = f64::from_le_bytes(
|
|
_data[_pos.._pos + 8].try_into().unwrap()
|
|
);
|
|
_pos += 8;
|
|
}
|
|
} else {
|
|
quote! {
|
|
let #field_name = f32::from_le_bytes(
|
|
_data[_pos.._pos + 4].try_into().unwrap()
|
|
);
|
|
_pos += 4;
|
|
}
|
|
};
|
|
|
|
let sz = size as usize;
|
|
let size_expr = quote! { #sz };
|
|
(dt, ser, deser, size_expr)
|
|
}
|
|
|
|
fn int_mapping(
|
|
field_name: &syn::Ident,
|
|
size: u32,
|
|
signed: bool,
|
|
) -> (
|
|
proc_macro2::TokenStream,
|
|
proc_macro2::TokenStream,
|
|
proc_macro2::TokenStream,
|
|
proc_macro2::TokenStream,
|
|
) {
|
|
let precision = (size * 8) as u16;
|
|
|
|
let dt = quote! {
|
|
clawhdf5_format::datatype::Datatype::FixedPoint {
|
|
size: #size,
|
|
byte_order: clawhdf5_format::datatype::DatatypeByteOrder::LittleEndian,
|
|
signed: #signed,
|
|
bit_offset: 0,
|
|
bit_precision: #precision,
|
|
}
|
|
};
|
|
|
|
let ser = quote! {
|
|
_buf.extend_from_slice(&self.#field_name.to_le_bytes());
|
|
};
|
|
|
|
let sz = size as usize;
|
|
let deser = match (size, signed) {
|
|
(1, true) => quote! {
|
|
let #field_name = _data[_pos] as i8;
|
|
_pos += 1;
|
|
},
|
|
(1, false) => quote! {
|
|
let #field_name = _data[_pos];
|
|
_pos += 1;
|
|
},
|
|
(2, true) => quote! {
|
|
let #field_name = i16::from_le_bytes(
|
|
_data[_pos.._pos + 2].try_into().unwrap()
|
|
);
|
|
_pos += 2;
|
|
},
|
|
(2, false) => quote! {
|
|
let #field_name = u16::from_le_bytes(
|
|
_data[_pos.._pos + 2].try_into().unwrap()
|
|
);
|
|
_pos += 2;
|
|
},
|
|
(4, true) => quote! {
|
|
let #field_name = i32::from_le_bytes(
|
|
_data[_pos.._pos + 4].try_into().unwrap()
|
|
);
|
|
_pos += 4;
|
|
},
|
|
(4, false) => quote! {
|
|
let #field_name = u32::from_le_bytes(
|
|
_data[_pos.._pos + 4].try_into().unwrap()
|
|
);
|
|
_pos += 4;
|
|
},
|
|
(8, true) => quote! {
|
|
let #field_name = i64::from_le_bytes(
|
|
_data[_pos.._pos + 8].try_into().unwrap()
|
|
);
|
|
_pos += 8;
|
|
},
|
|
(8, false) => quote! {
|
|
let #field_name = u64::from_le_bytes(
|
|
_data[_pos.._pos + 8].try_into().unwrap()
|
|
);
|
|
_pos += 8;
|
|
},
|
|
_ => quote! {
|
|
let mut _tmp = [0u8; #sz];
|
|
_tmp.copy_from_slice(&_data[_pos.._pos + #sz]);
|
|
let #field_name = _tmp;
|
|
_pos += #sz;
|
|
},
|
|
};
|
|
|
|
let sz = size as usize;
|
|
let size_expr = quote! { #sz };
|
|
(dt, ser, deser, size_expr)
|
|
}
|
|
|
|
fn bool_mapping(
|
|
field_name: &syn::Ident,
|
|
) -> (
|
|
proc_macro2::TokenStream,
|
|
proc_macro2::TokenStream,
|
|
proc_macro2::TokenStream,
|
|
proc_macro2::TokenStream,
|
|
) {
|
|
let dt = quote! {
|
|
clawhdf5_format::datatype::Datatype::FixedPoint {
|
|
size: 1,
|
|
byte_order: clawhdf5_format::datatype::DatatypeByteOrder::LittleEndian,
|
|
signed: false,
|
|
bit_offset: 0,
|
|
bit_precision: 8,
|
|
}
|
|
};
|
|
|
|
let ser = quote! {
|
|
_buf.push(if self.#field_name { 1u8 } else { 0u8 });
|
|
};
|
|
|
|
let deser = quote! {
|
|
let #field_name = _data[_pos] != 0;
|
|
_pos += 1;
|
|
};
|
|
|
|
let size_expr = quote! { 1usize };
|
|
(dt, ser, deser, size_expr)
|
|
}
|
|
|
|
fn array_mapping(
|
|
field_name: &syn::Ident,
|
|
elem_ty: &Type,
|
|
len_expr: &syn::Expr,
|
|
) -> syn::Result<(
|
|
proc_macro2::TokenStream,
|
|
proc_macro2::TokenStream,
|
|
proc_macro2::TokenStream,
|
|
proc_macro2::TokenStream,
|
|
)> {
|
|
let Type::Path(type_path) = elem_ty else {
|
|
return Err(syn::Error::new_spanned(
|
|
elem_ty,
|
|
"array element must be a primitive type for H5Type derive",
|
|
));
|
|
};
|
|
let elem_name = type_path.path.segments.last().unwrap().ident.to_string();
|
|
|
|
let (base_dt, elem_size, deser_one) = match elem_name.as_str() {
|
|
"f64" => (
|
|
quote! {
|
|
clawhdf5_format::datatype::Datatype::FloatingPoint {
|
|
size: 8,
|
|
byte_order: clawhdf5_format::datatype::DatatypeByteOrder::LittleEndian,
|
|
bit_offset: 0, bit_precision: 64,
|
|
exponent_location: 52, exponent_size: 11,
|
|
mantissa_location: 0, mantissa_size: 52,
|
|
exponent_bias: 1023,
|
|
}
|
|
},
|
|
8usize,
|
|
quote! { f64::from_le_bytes(_data[_pos.._pos + 8].try_into().unwrap()) },
|
|
),
|
|
"f32" => (
|
|
quote! {
|
|
clawhdf5_format::datatype::Datatype::FloatingPoint {
|
|
size: 4,
|
|
byte_order: clawhdf5_format::datatype::DatatypeByteOrder::LittleEndian,
|
|
bit_offset: 0, bit_precision: 32,
|
|
exponent_location: 23, exponent_size: 8,
|
|
mantissa_location: 0, mantissa_size: 23,
|
|
exponent_bias: 127,
|
|
}
|
|
},
|
|
4usize,
|
|
quote! { f32::from_le_bytes(_data[_pos.._pos + 4].try_into().unwrap()) },
|
|
),
|
|
"i8" => (int_dt_quote(1, true), 1usize, quote! { _data[_pos] as i8 }),
|
|
"i16" => (
|
|
int_dt_quote(2, true),
|
|
2usize,
|
|
quote! { i16::from_le_bytes(_data[_pos.._pos + 2].try_into().unwrap()) },
|
|
),
|
|
"i32" => (
|
|
int_dt_quote(4, true),
|
|
4usize,
|
|
quote! { i32::from_le_bytes(_data[_pos.._pos + 4].try_into().unwrap()) },
|
|
),
|
|
"i64" => (
|
|
int_dt_quote(8, true),
|
|
8usize,
|
|
quote! { i64::from_le_bytes(_data[_pos.._pos + 8].try_into().unwrap()) },
|
|
),
|
|
"u8" => (int_dt_quote(1, false), 1usize, quote! { _data[_pos] }),
|
|
"u16" => (
|
|
int_dt_quote(2, false),
|
|
2usize,
|
|
quote! { u16::from_le_bytes(_data[_pos.._pos + 2].try_into().unwrap()) },
|
|
),
|
|
"u32" => (
|
|
int_dt_quote(4, false),
|
|
4usize,
|
|
quote! { u32::from_le_bytes(_data[_pos.._pos + 4].try_into().unwrap()) },
|
|
),
|
|
"u64" => (
|
|
int_dt_quote(8, false),
|
|
8usize,
|
|
quote! { u64::from_le_bytes(_data[_pos.._pos + 8].try_into().unwrap()) },
|
|
),
|
|
_ => {
|
|
return Err(syn::Error::new_spanned(
|
|
elem_ty,
|
|
format!("unsupported array element type `{elem_name}` for H5Type derive"),
|
|
));
|
|
}
|
|
};
|
|
|
|
let dt = quote! {
|
|
clawhdf5_format::datatype::Datatype::Array {
|
|
base_type: Box::new(#base_dt),
|
|
dimensions: vec![#len_expr as u32],
|
|
}
|
|
};
|
|
|
|
let ser = quote! {
|
|
for _elem in &self.#field_name {
|
|
_buf.extend_from_slice(&_elem.to_le_bytes());
|
|
}
|
|
};
|
|
|
|
let deser = quote! {
|
|
let #field_name = {
|
|
let mut _arr = [Default::default(); #len_expr];
|
|
for _i in 0..#len_expr {
|
|
_arr[_i] = #deser_one;
|
|
_pos += #elem_size;
|
|
}
|
|
_arr
|
|
};
|
|
};
|
|
|
|
let size_expr = quote! { (#len_expr * #elem_size) };
|
|
Ok((dt, ser, deser, size_expr))
|
|
}
|
|
|
|
fn int_dt_quote(size: u32, signed: bool) -> proc_macro2::TokenStream {
|
|
let precision = (size * 8) as u16;
|
|
quote! {
|
|
clawhdf5_format::datatype::Datatype::FixedPoint {
|
|
size: #size,
|
|
byte_order: clawhdf5_format::datatype::DatatypeByteOrder::LittleEndian,
|
|
signed: #signed,
|
|
bit_offset: 0,
|
|
bit_precision: #precision,
|
|
}
|
|
}
|
|
}
|