use num_enum::{IntoPrimitive, TryFromPrimitive};
use serde::{Deserialize, Serialize};
use strum::VariantArray;
#[derive(
Clone,
Copy,
Debug,
PartialEq,
Eq,
Hash,
IntoPrimitive,
TryFromPrimitive,
VariantArray,
Serialize,
Deserialize,
)]
#[repr(u8)]
#[doc(alias("op_dtype_t"))]
pub enum OperandDataType {
#[doc(alias("dt_byte"))]
Byte = 0,
#[doc(alias("dt_word"))]
Word = 1,
#[doc(alias("dt_dword"))]
Dword = 2,
#[doc(alias("dt_float"))]
Float = 3,
#[doc(alias("dt_double"))]
Double = 4,
#[doc(alias("dt_tbyte"))]
Tbyte = 5,
#[doc(alias("dt_packreal"))]
PackReal = 6,
#[doc(alias("dt_qword"))]
Qword = 7,
#[doc(alias("dt_byte16"))]
Byte16 = 8,
#[doc(alias("dt_code"))]
Code = 9,
#[doc(alias("dt_void"))]
Void = 10,
#[doc(alias("dt_fword"))]
Fword = 11,
#[doc(alias("dt_bitfild"))]
BitField = 12,
#[doc(alias("dt_string"))]
String = 13,
#[doc(alias("dt_unicode"))]
Unicode = 14,
#[doc(alias("dt_ldbl"))]
Ldbl = 15,
#[doc(alias("dt_byte32"))]
Byte32 = 16,
#[doc(alias("dt_byte64"))]
Byte64 = 17,
#[doc(alias("dt_half"))]
Half = 18,
}
impl OperandDataType {
#[must_use]
pub fn bytes(self) -> Option<u32> {
Some(match self {
Self::Byte => 1,
Self::Word | Self::Half => 2,
Self::Dword | Self::Float => 4,
Self::Fword => 6,
Self::Double | Self::Qword => 8,
Self::Byte16 => 16,
Self::Byte32 => 32,
Self::Byte64 => 64,
_ => return None,
})
}
#[inline]
#[must_use]
pub fn is_float(self) -> bool {
matches!(
self,
Self::Float | Self::Double | Self::Tbyte | Self::Ldbl | Self::Half
)
}
}
impl std::fmt::Display for OperandDataType {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
Self::Byte => "byte",
Self::Word => "word",
Self::Dword => "dword",
Self::Float => "float",
Self::Double => "double",
Self::Tbyte => "tbyte",
Self::PackReal => "packed real",
Self::Qword => "qword",
Self::Byte16 => "byte16",
Self::Code => "code pointer",
Self::Void => "void",
Self::Fword => "fword",
Self::BitField => "bit field",
Self::String => "string pointer",
Self::Unicode => "unicode string pointer",
Self::Ldbl => "long double",
Self::Byte32 => "byte32",
Self::Byte64 => "byte64",
Self::Half => "half",
})
}
}
#[cfg(test)]
mod tests {
use assert2::assert;
use idakit_sys as sys;
use rstest::rstest;
use super::*;
#[test]
fn raw_roundtrips_every_variant() {
for &d in OperandDataType::VARIANTS {
assert!(OperandDataType::try_from(u8::from(d)).ok() == Some(d));
}
}
#[test]
fn dtype_ids_align_with_the_facade() {
let ids = sys::op_dtype_ids();
assert!(ids.len() == OperandDataType::VARIANTS.len());
for (i, &d) in OperandDataType::VARIANTS.iter().enumerate() {
assert!(
ids[i] == u8::from(d),
"data type {d:?}: facade dt_ {} != discriminant {}",
ids[i],
u8::from(d)
);
}
}
#[test]
fn try_from_rejects_unknown() {
assert!(OperandDataType::try_from(19).is_err());
assert!(OperandDataType::try_from(20).is_err());
assert!(OperandDataType::try_from(100).is_err());
assert!(OperandDataType::try_from(255).is_err());
}
mod proptests {
use proptest::prelude::*;
use super::*;
proptest! {
#[test]
fn try_from_matches_the_modelled_discriminant_set(byte: u8) {
let modelled = OperandDataType::VARIANTS.iter().any(|&v| u8::from(v) == byte);
prop_assert_eq!(OperandDataType::try_from(byte).is_ok(), modelled);
}
}
}
#[rstest]
#[case::byte(OperandDataType::Byte, Some(1))]
#[case::word(OperandDataType::Word, Some(2))]
#[case::dword(OperandDataType::Dword, Some(4))]
#[case::float(OperandDataType::Float, Some(4))]
#[case::double(OperandDataType::Double, Some(8))]
#[case::tbyte(OperandDataType::Tbyte, None)]
#[case::pack_real(OperandDataType::PackReal, None)]
#[case::qword(OperandDataType::Qword, Some(8))]
#[case::byte16(OperandDataType::Byte16, Some(16))]
#[case::code(OperandDataType::Code, None)]
#[case::void(OperandDataType::Void, None)]
#[case::fword(OperandDataType::Fword, Some(6))]
#[case::bit_field(OperandDataType::BitField, None)]
#[case::string(OperandDataType::String, None)]
#[case::unicode(OperandDataType::Unicode, None)]
#[case::ldbl(OperandDataType::Ldbl, None)]
#[case::byte32(OperandDataType::Byte32, Some(32))]
#[case::byte64(OperandDataType::Byte64, Some(64))]
#[case::half(OperandDataType::Half, Some(2))]
fn bytes_matches_every_variant(#[case] dtype: OperandDataType, #[case] expect: Option<u32>) {
assert!(dtype.bytes() == expect);
}
#[test]
fn float_classification() {
assert!(OperandDataType::Float.is_float());
assert!(OperandDataType::Half.is_float());
assert!(OperandDataType::Ldbl.is_float());
assert!(!OperandDataType::Dword.is_float());
assert!(!OperandDataType::Qword.is_float());
}
#[test]
fn display_renders_every_variant() {
for &d in OperandDataType::VARIANTS {
assert!(!d.to_string().is_empty());
}
assert!(OperandDataType::Qword.to_string() == "qword");
assert!(OperandDataType::Byte.to_string() == "byte");
}
#[test]
fn serde_roundtrips_every_variant() {
for &d in OperandDataType::VARIANTS {
let json = serde_json::to_string(&d).expect("serialize");
let back: OperandDataType = serde_json::from_str(&json).expect("deserialize");
assert!(back == d);
}
}
#[test]
fn hash_usable_in_set() {
use std::collections::HashSet;
let set: HashSet<OperandDataType> = OperandDataType::VARIANTS.iter().copied().collect();
assert!(set.len() == OperandDataType::VARIANTS.len());
}
}