open_pql/vm/
stack_value_num.rs

1use super::*;
2
3#[derive(Debug, Clone, Copy, From, TryInto)]
4pub enum VmStackValueNum {
5    Double(PQLDouble),
6    Long(PQLLong),
7    CardCount(PQLCardCount),
8}
9
10impl VmStackValueNum {
11    #[allow(clippy::cast_precision_loss)]
12    pub fn cast_dbl(self) -> PQLDouble {
13        match self {
14            Self::Double(v) => v,
15            Self::Long(v) => v as PQLDouble,
16            Self::CardCount(v) => PQLDouble::from(v),
17        }
18    }
19
20    #[allow(clippy::cast_possible_truncation)]
21    pub fn cast_int(self) -> PQLLong {
22        match self {
23            Self::Double(v) => v.floor() as PQLLong,
24            Self::Long(v) => v,
25            Self::CardCount(v) => PQLLong::from(v),
26        }
27    }
28}
29
30const EPSILON: PQLDouble = 1e-6;
31
32fn float_eq(l: PQLDouble, r: PQLDouble) -> bool {
33    (l - r).abs() <= EPSILON
34}
35
36impl PartialEq for VmStackValueNum {
37    fn eq(&self, other: &Self) -> bool {
38        match (self, other) {
39            (_, Self::Double(_)) | (Self::Double(_), _) => {
40                float_eq(self.cast_dbl(), other.cast_dbl())
41            }
42            _ => self.cast_int() == other.cast_int(),
43        }
44    }
45}
46
47impl PartialOrd for VmStackValueNum {
48    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
49        if self == other {
50            return Some(Ordering::Equal);
51        }
52
53        match (self, other) {
54            (_, Self::Double(_)) | (Self::Double(_), _) => {
55                self.cast_dbl().partial_cmp(&other.cast_dbl())
56            }
57            _ => self.cast_int().partial_cmp(&other.cast_int()),
58        }
59    }
60}
61
62// using generics causes stackoverflow
63macro_rules! impl_from {
64    ($ty:ty) => {
65        impl From<$ty> for VmStackValue {
66            fn from(v: $ty) -> Self {
67                VmStackValueNum::from(v).into()
68            }
69        }
70    };
71}
72
73impl_from!(PQLCardCount);
74impl_from!(PQLDouble);
75impl_from!(PQLLong);
76
77macro_rules! impl_try_into {
78    ($ty:ty) => {
79        impl TryFrom<VmStackValue> for $ty {
80            type Error = PQLError;
81
82            fn try_from(v: VmStackValue) -> Result<Self, Self::Error> {
83                if let VmStackValue::Num(v) = v {
84                    if let Ok(v) = v.try_into() {
85                        return Ok(v);
86                    }
87                }
88
89                Err(InternalError::BrokenStack.into())
90            }
91        }
92    };
93}
94
95impl_try_into!(PQLCardCount);
96impl_try_into!(PQLDouble);
97impl_try_into!(PQLLong);
98
99macro_rules! impl_op {
100    ($ty:ty, $name:ident, $op:tt) => {
101        impl $ty for VmStackValueNum {
102            type Output = Self;
103
104            fn $name(self, rhs: Self) -> Self::Output {
105                match (self, rhs) {
106                    (_, Self::Double(_)) | (Self::Double(_), _) => {
107                        Self::from(self.cast_dbl() $op rhs.cast_dbl())
108                    }
109                    _ => Self::from(self.cast_int() $op rhs.cast_int()),
110                }
111            }
112        }
113    };
114}
115
116impl Div for VmStackValueNum {
117    type Output = Self;
118
119    fn div(self, rhs: Self) -> Self::Output {
120        Self::from(self.cast_dbl() / rhs.cast_dbl())
121    }
122}
123
124impl_op!(Add, add, +);
125impl_op!(Sub, sub, -);
126impl_op!(Mul, mul, *);
127
128#[cfg(test)]
129mod tests {
130    use VmStackValueNum::*;
131
132    use super::*;
133    use crate::*;
134
135    impl Arbitrary for VmStackValueNum {
136        #[allow(clippy::cast_precision_loss)]
137        fn arbitrary(g: &mut quickcheck::Gen) -> Self {
138            match g.choose(&[0, 1, 2]).unwrap() {
139                0 => Double(PQLLong::arbitrary(g) as PQLDouble / 10.0), // avoid NaN
140                1 => Long(i32::arbitrary(g).into()), // avoid overflow
141                _ => CardCount(PQLCardCount::arbitrary(g)),
142            }
143        }
144    }
145
146    #[test]
147    fn test_eq() {
148        let v = Double(100.0 + EPSILON / 10.0);
149
150        assert_eq!(Double(1.0).cast_int(), 1);
151
152        assert_eq!(v, Double(100.0));
153        assert_eq!(v, Long(100));
154        assert_eq!(v, CardCount(100));
155
156        assert!(v <= Double(100.0));
157        assert!(v <= Long(100));
158        assert!(v <= CardCount(100));
159        assert!(v >= Double(100.0));
160        assert!(v >= Long(100));
161        assert!(v >= CardCount(100));
162    }
163
164    #[test]
165    fn test_cmp() {
166        let v = Double(100.0);
167
168        assert!(v >= Long(99));
169        assert!(v > CardCount(99));
170        assert!(v <= Long(101));
171        assert!(v < CardCount(101));
172        assert!(Long(99) < CardCount(101));
173    }
174
175    #[test]
176    fn test_from_and_into() {
177        assert_eq!(VmStackValue::Num(Long(0)), PQLLong::from(0).into());
178        assert_eq!(Ok(0), PQLLong::try_from(VmStackValue::Num(Long(0))));
179        assert!(PQLLong::try_from(VmStackValue::Rank(Some(Rank::R2))).is_err());
180        assert!(PQLLong::try_from(VmStackValue::Num(Double(0.0))).is_err());
181    }
182}