Skip to main content

mint_cli/layout/
conversions.rs

1use super::entry::ScalarType;
2use super::error::LayoutError;
3use super::settings::{EndianBytes, Endianness};
4use super::value::DataValue;
5
6macro_rules! impl_try_from_data_value {
7    ($($t:ty),* $(,)?) => {$(
8        impl TryFrom<&DataValue> for $t {
9            type Error = LayoutError;
10            fn try_from(value: &DataValue) -> Result<Self, LayoutError> {
11                match value {
12                    DataValue::Bool(val) => {
13                        let n: u8 = if *val { 1 } else { 0 };
14                        Ok(n as $t)
15                    }
16                    DataValue::U64(val) => Ok(*val as $t),
17                    DataValue::I64(val) => Ok(*val as $t),
18                    DataValue::F64(val) => Ok(*val as $t),
19                    DataValue::Str(_) => {
20                        return Err(LayoutError::DataValueExportFailed(
21                            "Cannot convert string to scalar type.".to_string(),
22                        ));
23                    }
24                }
25            }
26        }
27    )* }; }
28
29impl_try_from_data_value!(u8, u16, u32, u64, i8, i16, i32, i64, i128, f32, f64);
30
31pub trait TryFromStrict<T>: Sized {
32    fn try_from_strict(value: T) -> Result<Self, LayoutError>;
33}
34
35macro_rules! err {
36    ($msg:expr) => {
37        LayoutError::DataValueExportFailed($msg.to_string())
38    };
39}
40
41macro_rules! impl_try_from_strict_unsigned {
42    ($($t:ty),* $(,)?) => {$(
43        impl TryFromStrict<&DataValue> for $t {
44            fn try_from_strict(value: &DataValue) -> Result<Self, LayoutError> {
45                match value {
46                    DataValue::U64(v) => <Self as TryFrom<u64>>::try_from(*v)
47                        .map_err(|_| err!(format!("u64 value {} out of range for {}", v, stringify!($t)))),
48                    DataValue::I64(v) => {
49                        if *v < 0 { return Err(err!("negative integer cannot convert to unsigned in strict mode")); }
50                        <Self as TryFrom<u64>>::try_from(*v as u64)
51                            .map_err(|_| err!(format!("i64 value {} out of range for {}", v, stringify!($t))))
52                    }
53                    DataValue::F64(v) => {
54                        if !v.is_finite() { return Err(err!("non-finite float cannot convert to integer in strict mode")); }
55                        if v.fract() != 0.0 { return Err(err!("float to integer conversion not allowed unless value is an exact integer")); }
56                        if *v < 0.0 || *v > (<$t>::MAX as f64) { return Err(err!(format!("float value {} out of range for {}", v, stringify!($t)))); }
57                        Ok(*v as $t)
58                    }
59                    DataValue::Bool(b) => {
60                        let n: u8 = if *b { 1 } else { 0 };
61                        Ok(n as $t)
62                    }
63                    DataValue::Str(_) => Err(err!("Cannot convert string to scalar type.")),
64                }
65            }
66        }
67    )*};
68}
69
70macro_rules! impl_try_from_strict_signed {
71    ($($t:ty),* $(,)?) => {$(
72        impl TryFromStrict<&DataValue> for $t {
73            fn try_from_strict(value: &DataValue) -> Result<Self, LayoutError> {
74                match value {
75                    DataValue::U64(v) => {
76                        <Self as TryFrom<i128>>::try_from(*v as i128)
77                            .map_err(|_| err!(format!("u64 value {} out of range for {}", v, stringify!($t))))
78                    }
79                    DataValue::I64(v) => <Self as TryFrom<i64>>::try_from(*v)
80                        .map_err(|_| err!(format!("i64 value {} out of range for {}", v, stringify!($t)))),
81                    DataValue::F64(v) => {
82                        if !v.is_finite() { return Err(err!("non-finite float cannot convert to integer in strict mode")); }
83                        if v.fract() != 0.0 { return Err(err!("float to integer conversion not allowed unless value is an exact integer")); }
84                        if *v < (<$t>::MIN as f64) || *v > (<$t>::MAX as f64) { return Err(err!(format!("float value {} out of range for {}", v, stringify!($t)))); }
85                        Ok(*v as $t)
86                    }
87                    DataValue::Bool(b) => {
88                        let n: u8 = if *b { 1 } else { 0 };
89                        Ok(n as $t)
90                    }
91                    DataValue::Str(_) => Err(err!("Cannot convert string to scalar type.")),
92                }
93            }
94        }
95    )*};
96}
97
98macro_rules! impl_try_from_strict_float_targets {
99    ($t:ty) => {
100        impl TryFromStrict<&DataValue> for $t {
101            fn try_from_strict(value: &DataValue) -> Result<Self, LayoutError> {
102                match value {
103                    DataValue::F64(v) => {
104                        if !v.is_finite() {
105                            return Err(err!("non-finite float not allowed in strict mode"));
106                        }
107                        let out = *v as $t;
108                        if out.is_finite() {
109                            Ok(out)
110                        } else {
111                            Err(err!(format!(
112                                "float value {} out of range for {}",
113                                v,
114                                stringify!($t)
115                            )))
116                        }
117                    }
118                    DataValue::U64(v) => {
119                        let out = (*v as $t);
120                        if !out.is_finite() {
121                            return Err(err!("integer to float produced non-finite value"));
122                        }
123                        // exactness check via round-trip
124                        if (out as u64) == *v {
125                            Ok(out)
126                        } else {
127                            Err(err!(
128                                "lossy integer to float conversion not allowed in strict mode"
129                            ))
130                        }
131                    }
132                    DataValue::I64(v) => {
133                        let out = (*v as $t);
134                        if !out.is_finite() {
135                            return Err(err!("integer to float produced non-finite value"));
136                        }
137                        if (out as i64) == *v {
138                            Ok(out)
139                        } else {
140                            Err(err!(
141                                "lossy integer to float conversion not allowed in strict mode"
142                            ))
143                        }
144                    }
145                    DataValue::Bool(b) => {
146                        let out: $t = if *b { 1.0 } else { 0.0 };
147                        Ok(out)
148                    }
149                    DataValue::Str(_) => Err(err!("Cannot convert string to scalar type.")),
150                }
151            }
152        }
153    };
154}
155
156impl_try_from_strict_unsigned!(u8, u16, u32, u64);
157impl_try_from_strict_signed!(i8, i16, i32, i64, i128);
158impl_try_from_strict_float_targets!(f32);
159impl TryFromStrict<&DataValue> for f64 {
160    fn try_from_strict(value: &DataValue) -> Result<Self, LayoutError> {
161        match value {
162            DataValue::F64(v) => Ok(*v),
163            DataValue::U64(v) => {
164                let out = *v as f64;
165                if (out as u64) == *v {
166                    Ok(out)
167                } else {
168                    Err(err!(
169                        "lossy integer to float conversion not allowed in strict mode"
170                    ))
171                }
172            }
173            DataValue::I64(v) => {
174                let out = *v as f64;
175                if (out as i64) == *v {
176                    Ok(out)
177                } else {
178                    Err(err!(
179                        "lossy integer to float conversion not allowed in strict mode"
180                    ))
181                }
182            }
183            DataValue::Bool(b) => {
184                let out = if *b { 1.0 } else { 0.0 };
185                Ok(out)
186            }
187            DataValue::Str(_) => Err(err!("Cannot convert string to scalar type.")),
188        }
189    }
190}
191
192/// Converts a DataValue to an i128 for bitfield packing, with range clamping/checking.
193///
194/// - `bits`: field width in bits (must be > 0)
195/// - `signed`: whether to interpret as two's complement signed field
196/// - `strict`: if true, out-of-range or non-integer floats produce errors; otherwise saturate
197pub fn clamp_bitfield_value(
198    value: &DataValue,
199    bits: usize,
200    signed: bool,
201    strict: bool,
202) -> Result<i128, LayoutError> {
203    let raw: i128 = if strict {
204        i128::try_from_strict(value)?
205    } else {
206        i128::try_from(value)?
207    };
208
209    let (min, max) = if signed {
210        let half = 1i128 << (bits - 1);
211        (-half, half - 1)
212    } else {
213        (0, (1i128 << bits) - 1)
214    };
215
216    if strict && (raw < min || raw > max) {
217        return Err(LayoutError::BitfieldOutOfRange {
218            value: raw,
219            bits,
220            signedness: if signed { "signed" } else { "unsigned" },
221            min,
222            max,
223        });
224    }
225    Ok(raw.clamp(min, max))
226}
227
228pub fn convert_value_to_bytes(
229    value: &DataValue,
230    scalar_type: ScalarType,
231    endianness: &Endianness,
232    strict: bool,
233) -> Result<Vec<u8>, LayoutError> {
234    macro_rules! to_bytes {
235        ($t:ty) => {{
236            let val: $t = if strict {
237                <$t as TryFromStrict<&DataValue>>::try_from_strict(value)?
238            } else {
239                <$t as TryFrom<&DataValue>>::try_from(value)?
240            };
241            Ok(val.to_endian_bytes(endianness))
242        }};
243    }
244
245    match scalar_type {
246        ScalarType::U8 => to_bytes!(u8),
247        ScalarType::I8 => to_bytes!(i8),
248        ScalarType::U16 => to_bytes!(u16),
249        ScalarType::I16 => to_bytes!(i16),
250        ScalarType::U32 => to_bytes!(u32),
251        ScalarType::I32 => to_bytes!(i32),
252        ScalarType::U64 => to_bytes!(u64),
253        ScalarType::I64 => to_bytes!(i64),
254        ScalarType::F32 => to_bytes!(f32),
255        ScalarType::F64 => to_bytes!(f64),
256    }
257}