erased_cells/
ctype.rs

1use crate::error::Error;
2use crate::{with_ct, CellValue};
3use num_traits::{One, Zero};
4#[cfg(feature = "serde")]
5use serde::{Deserialize, Serialize};
6use std::fmt::{Debug, Display, Formatter};
7use std::mem;
8use std::str::FromStr;
9
10/// CellType enum constructor.
11macro_rules! cv_enum {
12    ( $(($id:ident, $_p:ident)),*) => {
13        /// Cell-type variants
14        #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
15        #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
16        #[repr(u8)]
17        pub enum CellType { $($id),* }
18    }
19}
20with_ct!(cv_enum);
21
22/// `Display` is the same as `Debug`.
23impl Display for CellType {
24    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
25        Debug::fmt(self, f)
26    }
27}
28
29impl FromStr for CellType {
30    type Err = Error;
31
32    fn from_str(s: &str) -> Result<Self, Self::Err> {
33        macro_rules! str_match {
34            ( $( ($ct:ident, $_p:ident) ),* ) => {
35                match s {
36                    $( stringify!($ct) => Ok(CellType::$ct), )*
37                    o => Err(Error::ParseError(o.into(), "CellType")),
38                }
39            };
40        }
41        with_ct!(str_match)
42    }
43}
44
45impl CellType {
46    /// Get an iterator over all the valid enumeration values.
47    pub fn iter() -> impl Iterator<Item = CellType> {
48        macro_rules! array {
49           ( $( ($id:ident, $_p:ident) ),+) => { [ $( CellType::$id, )+ ] };
50        }
51        with_ct!(array).into_iter()
52    }
53
54    /// Determine if `self` is integral or floating-point.
55    pub fn is_integral(&self) -> bool {
56        match self {
57            CellType::UInt8 => true,
58            CellType::UInt16 => true,
59            CellType::UInt32 => true,
60            CellType::UInt64 => true,
61            CellType::Int8 => true,
62            CellType::Int16 => true,
63            CellType::Int32 => true,
64            CellType::Int64 => true,
65            CellType::Float32 => false,
66            CellType::Float64 => false,
67        }
68    }
69
70    /// Determine if `self` is signed or unsigned.
71    pub fn is_signed(&self) -> bool {
72        match self {
73            CellType::UInt8 => false,
74            CellType::UInt16 => false,
75            CellType::UInt32 => false,
76            CellType::UInt64 => false,
77            CellType::Int8 => true,
78            CellType::Int16 => true,
79            CellType::Int32 => true,
80            CellType::Int64 => true,
81            CellType::Float32 => true,
82            CellType::Float64 => true,
83        }
84    }
85
86    /// Number of bytes needed to encode `self`.
87    pub fn size_of(&self) -> usize {
88        macro_rules! size_of {
89            ($( ($id:ident, $p:ident) ),* ) => {
90                match self {
91                    $(CellType::$id => mem::size_of::<$p>()),*
92                }
93            };
94        }
95        with_ct!(size_of)
96    }
97
98    /// Select the `CellType` that can numerically contain both `self` and `other`.
99    pub fn union(self, other: Self) -> Self {
100        let min_bytes = {
101            match (self.is_integral(), other.is_integral()) {
102                (true, false) => other.size_of().max(2 * self.size_of()),
103                (false, true) => self.size_of().max(2 * other.size_of()),
104                _ => match (self.is_signed(), other.is_signed()) {
105                    (true, false) => self.size_of().max(2 * other.size_of()),
106                    (false, true) => other.size_of().max(2 * self.size_of()),
107                    _ => self.size_of().max(other.size_of()),
108                },
109            }
110        };
111        let signed = self.is_signed() || other.is_signed();
112        let integral = self.is_integral() && other.is_integral();
113        //dbg!(min_bytes, signed, integral);
114        match (min_bytes, signed, integral) {
115            (1, false, true) => Self::UInt8,
116            (1, true, true) => Self::Int8,
117            (2, false, true) => Self::UInt16,
118            (2, true, true) => Self::Int16,
119            (4, false, true) => Self::UInt32,
120            (4, true, true) => Self::Int32,
121            (4, _, false) => Self::Float32,
122            (8, false, true) => Self::UInt64,
123            (8, true, true) => Self::Int64,
124            _ => Self::Float64,
125        }
126    }
127
128    /// Determine of `self` can fit within `other`.
129    pub fn can_fit_into(self, other: Self) -> bool {
130        self.union(other) == other
131    }
132
133    /// Construct the zero value for a variant.
134    pub fn zero(&self) -> CellValue {
135        macro_rules! zero {
136           ( $( ($id:ident, $p:ident) ),+) => {
137               match self {
138                   $(Self::$id => <$p>::zero().into(),)*
139               }
140           }
141        }
142        with_ct!(zero)
143    }
144
145    /// Construct the one value for a variant.
146    pub fn one(&self) -> CellValue {
147        macro_rules! one {
148           ( $( ($id:ident, $p:ident) ),+) => {
149               match self {
150                   $(Self::$id => <$p>::one().into(),)*
151               }
152           }
153        }
154        with_ct!(one)
155    }
156
157    /// Determine the minimum value that can be represented by `self`.
158    pub fn min_value(&self) -> CellValue {
159        macro_rules! mins {
160            ( $( ($id:ident, $p:ident) ),* ) => {
161                match self {
162                    $( CellType::$id => $p::MIN.into(), )*
163                }
164            };
165        }
166        with_ct!(mins)
167    }
168
169    /// Determine the maximum value that can be represented by `self`.
170    pub fn max_value(&self) -> CellValue {
171        macro_rules! maxs {
172            ( $( ($id:ident, $p:ident) ),* ) => {
173                match self {
174                    $( CellType::$id => $p::MAX.into(), )*
175                }
176            };
177        }
178        with_ct!(maxs)
179    }
180}
181
182#[cfg(test)]
183mod tests {
184    use crate::{with_ct, CellType};
185    use std::str::FromStr;
186
187    #[test]
188    fn can_union() {
189        // reflexivity
190        assert_eq!(CellType::UInt8.union(CellType::UInt8), CellType::UInt8);
191        assert_eq!(CellType::UInt16.union(CellType::UInt16), CellType::UInt16);
192        assert_eq!(
193            CellType::Float32.union(CellType::Float32),
194            CellType::Float32
195        );
196        assert_eq!(
197            CellType::Float64.union(CellType::Float64),
198            CellType::Float64
199        );
200
201        // symmetry
202        assert_eq!(CellType::Int16.union(CellType::Float32), CellType::Float32);
203        assert_eq!(CellType::Float32.union(CellType::Int16), CellType::Float32);
204        // widening
205        assert_eq!(CellType::UInt8.union(CellType::UInt16), CellType::UInt16);
206        assert_eq!(CellType::Int32.union(CellType::Float32), CellType::Float64);
207    }
208
209    #[test]
210    fn is_integral() {
211        assert!(CellType::UInt8.is_integral());
212        assert!(CellType::UInt16.is_integral());
213        assert!(!CellType::Float32.is_integral());
214        assert!(!CellType::Float64.is_integral());
215    }
216
217    #[test]
218    fn size() {
219        assert_eq!(CellType::Int8.size_of(), 1);
220        assert_eq!(CellType::UInt8.size_of(), 1);
221        assert_eq!(CellType::Int16.size_of(), 2);
222        assert_eq!(CellType::UInt16.size_of(), 2);
223        assert_eq!(CellType::Int32.size_of(), 4);
224        assert_eq!(CellType::UInt32.size_of(), 4);
225        assert_eq!(CellType::Int64.size_of(), 8);
226        assert_eq!(CellType::UInt64.size_of(), 8);
227        assert_eq!(CellType::Float32.size_of(), 4);
228        assert_eq!(CellType::Float64.size_of(), 8);
229    }
230
231    #[test]
232    fn has_min_max() {
233        // Confirm min/max returns correct values.
234        macro_rules! test {
235            ( $( ($ct:ident, $p:ident) ),* ) => {
236                $(
237                    assert_eq!(CellType::$ct.min_value(), $p::MIN.into(), "min");
238                    assert_eq!(CellType::$ct.max_value(), $p::MAX.into(), "max");
239                )*
240            };
241        }
242        with_ct!(test);
243    }
244
245    #[test]
246    fn can_string() {
247        // Confirm simple serialization.
248        macro_rules! test {
249            ( $( ($ct:ident, $p:ident) ),* ) => {
250                $( assert_eq!(CellType::$ct.to_string(), stringify!($ct)); )*
251            };
252        }
253        with_ct!(test);
254
255        // Test round-trip conversion to/from String
256        for ct in CellType::iter() {
257            let stringed = ct.to_string();
258            let parsed = CellType::from_str(&stringed);
259            assert!(parsed.is_ok(), "{stringed}");
260            assert_eq!(parsed.unwrap(), ct, "{stringed}");
261        }
262
263        assert!(CellType::from_str("UInt57").is_err());
264    }
265
266    #[test]
267    fn zero_one() {
268        macro_rules! test {
269            ( $( ($ct:ident, $p:ident) ),* ) => {
270                $({
271                    let zero = CellType::$ct.zero();
272                    let one = CellType::$ct.one();
273                    assert_eq!(one + zero, one);
274                })*
275            };
276        }
277        with_ct!(test);
278    }
279}