use core::fmt;
use serde::de::{self, MapAccess, Visitor};
use serde::ser::SerializeStruct;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::mat::value::ComplexTag;
macro_rules! complex_types {
($($name:ident => $scalar:ty, $sentinel:ident, $array_sentinel:ident,
$array_fn:ident, $tag:ident, $matlab:literal),* $(,)?) => {
$(
#[doc = concat!(
"Sentinel struct name for [`", stringify!($name), "`]."
)]
pub(crate) const $sentinel: &str =
concat!("__hdf5_pure_mat_", stringify!($name), "__");
#[doc = concat!(
"Sentinel newtype name for a whole slice of [`", stringify!($name),
"`], written by [`", stringify!($array_fn), "`]."
)]
pub(crate) const $array_sentinel: &str =
concat!("__hdf5_pure_mat_", stringify!($name), "_array__");
#[doc = concat!(
"A complex number with `", stringify!($scalar),
"` components, stored as a MATLAB `", $matlab,
"` complex array."
)]
#[derive(Debug, Clone, Copy, PartialEq)]
#[repr(C)]
pub struct $name {
/// Real part.
pub re: $scalar,
pub im: $scalar,
}
impl $name {
pub const fn new(re: $scalar, im: $scalar) -> Self {
Self { re, im }
}
}
impl Serialize for $name {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let mut s = serializer.serialize_struct($sentinel, 2)?;
s.serialize_field("real", &self.re)?;
s.serialize_field("imag", &self.im)?;
s.end()
}
}
impl<'de> Deserialize<'de> for $name {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct ComplexVisitor;
impl<'de> Visitor<'de> for ComplexVisitor {
type Value = $name;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(concat!(
stringify!($name),
" struct with fields `real` and `imag`"
))
}
fn visit_map<A: MapAccess<'de>>(
self,
mut map: A,
) -> Result<$name, A::Error> {
let mut re: Option<$scalar> = None;
let mut im: Option<$scalar> = None;
while let Some(key) = map.next_key::<String>()? {
match key.as_str() {
"real" => re = Some(map.next_value()?),
"imag" => im = Some(map.next_value()?),
_ => {
let _: serde::de::IgnoredAny = map.next_value()?;
}
}
}
Ok($name {
re: re.ok_or_else(|| de::Error::missing_field("real"))?,
im: im.ok_or_else(|| de::Error::missing_field("imag"))?,
})
}
}
deserializer.deserialize_struct(
$sentinel,
&["real", "imag"],
ComplexVisitor,
)
}
}
#[doc = concat!(
"Serialize a slice of complex `", stringify!($scalar),
"` as one MATLAB `", $matlab, "` complex array."
)]
#[doc = concat!(
"Takes any [`ComplexElement`] whose `Component` is `",
stringify!($scalar), "` — this module's [`", stringify!($name),
"`], `num_complex::Complex<", stringify!($scalar),
">` under the `num-complex` feature, or your own type. The \
component is part of the bound, so a same-width class cannot \
slip through: `i32` parts will not compile here."
)]
#[doc = concat!(
"- **An empty slice keeps its component class**, writing an \
empty `", $matlab, "` array where a plain `Vec<",
stringify!($name), ">` writes an empty `double`. That is the \
only case where the annotation changes the file."
)]
#[doc = concat!(" #[serde(serialize_with = \"mat::complex::",
stringify!($array_fn), "\")]")]
#[doc = concat!(" samples: Vec<mat::", stringify!($name), ">,")]
pub fn $array_fn<S, T>(data: &[T], serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
T: ComplexElement<Component = $scalar>,
{
assert_eq!(
core::mem::size_of::<T>(),
2 * core::mem::size_of::<$scalar>(),
concat!(
"mat::complex::", stringify!($array_fn),
": ComplexElement impl has the wrong element size",
),
);
let raw: &[u8] = unsafe {
core::slice::from_raw_parts(
data.as_ptr().cast::<u8>(),
core::mem::size_of_val(data),
)
};
serializer.serialize_newtype_struct($array_sentinel, &RawComplexBytes(raw))
}
unsafe impl ComplexElement for $name {
type Component = $scalar;
}
#[cfg(feature = "num-complex")]
unsafe impl ComplexElement for num_complex::Complex<$scalar> {
type Component = $scalar;
}
)*
pub(crate) fn complex_tag_for_sentinel(name: &str) -> Option<ComplexTag> {
match name {
$($sentinel => Some(ComplexTag::$tag),)*
_ => None,
}
}
pub(crate) fn complex_tag_for_array_sentinel(name: &str) -> Option<ComplexTag> {
match name {
$($array_sentinel => Some(ComplexTag::$tag),)*
_ => None,
}
}
};
}
pub unsafe trait ComplexElement: Copy {
type Component;
}
struct RawComplexBytes<'a>(&'a [u8]);
impl Serialize for RawComplexBytes<'_> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_bytes(self.0)
}
}
complex_types! {
Complex64 => f64, COMPLEX64_SENTINEL, COMPLEX64_ARRAY_SENTINEL, f64_array, F64, "double",
Complex32 => f32, COMPLEX32_SENTINEL, COMPLEX32_ARRAY_SENTINEL, f32_array, F32, "single",
ComplexI64 => i64, COMPLEX_I64_SENTINEL, COMPLEX_I64_ARRAY_SENTINEL, i64_array, I64, "int64",
ComplexI32 => i32, COMPLEX_I32_SENTINEL, COMPLEX_I32_ARRAY_SENTINEL, i32_array, I32, "int32",
ComplexI16 => i16, COMPLEX_I16_SENTINEL, COMPLEX_I16_ARRAY_SENTINEL, i16_array, I16, "int16",
ComplexI8 => i8, COMPLEX_I8_SENTINEL, COMPLEX_I8_ARRAY_SENTINEL, i8_array, I8, "int8",
ComplexU64 => u64, COMPLEX_U64_SENTINEL, COMPLEX_U64_ARRAY_SENTINEL, u64_array, U64, "uint64",
ComplexU32 => u32, COMPLEX_U32_SENTINEL, COMPLEX_U32_ARRAY_SENTINEL, u32_array, U32, "uint32",
ComplexU16 => u16, COMPLEX_U16_SENTINEL, COMPLEX_U16_ARRAY_SENTINEL, u16_array, U16, "uint16",
ComplexU8 => u8, COMPLEX_U8_SENTINEL, COMPLEX_U8_ARRAY_SENTINEL, u8_array, U8, "uint8",
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn construct_and_compare() {
let a = Complex64::new(1.0, -2.0);
let b = Complex64 { re: 1.0, im: -2.0 };
assert_eq!(a, b);
}
#[test]
fn each_sentinel_names_its_own_component_class() {
assert_eq!(
complex_tag_for_sentinel(COMPLEX_I16_SENTINEL),
Some(ComplexTag::I16)
);
assert_eq!(
complex_tag_for_sentinel(COMPLEX64_SENTINEL),
Some(ComplexTag::F64)
);
assert_eq!(complex_tag_for_sentinel("SomeUserStruct"), None);
}
#[test]
fn the_array_helpers_read_their_slice_without_undefined_behavior() {
use crate::mat::options::Options;
use crate::mat::ser::value_ser::ValueSerializer;
use crate::mat::value::{ComplexVec, MatValue};
let opts = Options::default();
let pairs = [ComplexI16::new(1, -2), ComplexI16::new(3, -4)];
assert_eq!(
i16_array(&pairs, ValueSerializer::new(&opts)).unwrap(),
MatValue::ComplexVec1D(ComplexVec::I16(vec![(1, -2), (3, -4)])),
);
assert_eq!(
i16_array(&[] as &[ComplexI16], ValueSerializer::new(&opts)).unwrap(),
MatValue::ComplexVec1D(ComplexVec::I16(Vec::new())),
);
let wide = [Complex64::new(1.5, -2.5)];
assert_eq!(
f64_array(&wide, ValueSerializer::new(&opts)).unwrap(),
MatValue::ComplexVec1D(ComplexVec::F64(vec![(1.5, -2.5)])),
);
}
#[test]
fn sentinels_are_distinct() {
let all = [
COMPLEX64_SENTINEL,
COMPLEX32_SENTINEL,
COMPLEX_I64_SENTINEL,
COMPLEX_I32_SENTINEL,
COMPLEX_I16_SENTINEL,
COMPLEX_I8_SENTINEL,
COMPLEX_U64_SENTINEL,
COMPLEX_U32_SENTINEL,
COMPLEX_U16_SENTINEL,
COMPLEX_U8_SENTINEL,
];
let unique: std::collections::HashSet<&str> = all.iter().copied().collect();
assert_eq!(unique.len(), all.len());
}
}