Skip to main content

pax_runtime_api/pax_value/
arithmetic.rs

1use std::{
2    cmp::Ordering,
3    ops::{Add, Div, Mul, Neg, Not, Rem, Sub},
4};
5
6use crate::{Numeric, PaxValue, Percent, Size, ToPaxValue};
7
8use super::{PaxAny, ToFromPaxAny};
9
10const ANY_ARITH_UNSUPPORTED: &'static str =
11    "types that are not representable as PaxValues are not supported in arithmetic expressions";
12//----------------------------PaxValue Arithmetic-----------------------------
13impl Add for PaxValue {
14    type Output = Self;
15
16    fn add(self, rhs: Self) -> Self::Output {
17        match (self, rhs) {
18            // Basic types
19            (PaxValue::Numeric(a), PaxValue::Numeric(b)) => (a + b).to_pax_value(),
20            (PaxValue::String(a), PaxValue::String(b)) => (a + &b).to_pax_value(),
21            (PaxValue::String(a), PaxValue::Numeric(b)) => (a + &b.to_string()).to_pax_value(),
22            (PaxValue::Numeric(a), PaxValue::String(b)) => (a.to_string() + &b).to_pax_value(),
23
24            // Size and Percent
25            (PaxValue::Size(a), PaxValue::Size(b)) => (a + b).to_pax_value(),
26            (PaxValue::Percent(a), PaxValue::Percent(b)) => (Percent(a.0 + b.0)).to_pax_value(),
27            (PaxValue::Percent(a), PaxValue::Size(b))
28            | (PaxValue::Size(b), PaxValue::Percent(a)) => (Size::Percent(a.0) + b).to_pax_value(),
29            (PaxValue::Bool(a), PaxValue::Numeric(b))
30            | (PaxValue::Numeric(b), PaxValue::Bool(a)) => {
31                (Numeric::I64(a as i64) + b).to_pax_value()
32            }
33            (PaxValue::Size(a), PaxValue::Numeric(b))
34            | (PaxValue::Numeric(b), PaxValue::Size(a)) => match a {
35                Size::Pixels(px) => Size::Pixels(px + b).to_pax_value(),
36                Size::Percent(per) => Size::Percent(per + b).to_pax_value(),
37                Size::Combined(px, per) => Size::Combined(px + b, per + b).to_pax_value(),
38            },
39            (PaxValue::Numeric(a), PaxValue::Percent(b)) => Size::Combined(a, b.0).to_pax_value(),
40            (PaxValue::Percent(a), PaxValue::Numeric(b)) => Size::Combined(b, a.0).to_pax_value(),
41            (PaxValue::Rotation(a), PaxValue::Rotation(b)) => (a + b).to_pax_value(),
42            (a, b) => {
43                log::warn!("can't add {:?} and {:?}", a, b);
44                PaxValue::default()
45            }
46        }
47    }
48}
49
50impl Mul for PaxValue {
51    type Output = Self;
52
53    fn mul(self, rhs: Self) -> Self::Output {
54        match (self, rhs) {
55            (PaxValue::Numeric(a), PaxValue::Numeric(b)) => (a * b).to_pax_value(),
56            (PaxValue::Bool(a), PaxValue::Numeric(b))
57            | (PaxValue::Numeric(b), PaxValue::Bool(a)) => {
58                (Numeric::I64(a as i64) * b).to_pax_value()
59            }
60            (PaxValue::Bool(a), PaxValue::Percent(b))
61            | (PaxValue::Percent(b), PaxValue::Bool(a)) => {
62                if a {
63                    return PaxValue::Percent(b);
64                } else {
65                    return PaxValue::Percent(Percent(0.into()));
66                }
67            }
68            (PaxValue::Size(a), PaxValue::Numeric(b))
69            | (PaxValue::Numeric(b), PaxValue::Size(a)) => match a {
70                Size::Pixels(px) => Size::Pixels(px * b).to_pax_value(),
71                Size::Percent(per) => Size::Percent(per * b).to_pax_value(),
72                Size::Combined(px, per) => Size::Combined(px * b, per * b).to_pax_value(),
73            },
74            (a, b) => {
75                log::warn!("can't multiply {:?} and {:?}", a, b);
76                PaxValue::default()
77            }
78        }
79    }
80}
81
82impl Sub for PaxValue {
83    type Output = Self;
84
85    fn sub(self, rhs: Self) -> Self::Output {
86        match (self, rhs) {
87            (PaxValue::Numeric(a), PaxValue::Numeric(b)) => (a - b).to_pax_value(),
88            // Size and Percent
89            (PaxValue::Size(a), PaxValue::Size(b)) => (a - b).to_pax_value(),
90            (PaxValue::Percent(a), PaxValue::Percent(b)) => (Percent(a.0 - b.0)).to_pax_value(),
91            (PaxValue::Percent(a), PaxValue::Size(b)) => (Size::Percent(a.0) - b).to_pax_value(),
92            (PaxValue::Size(a), PaxValue::Percent(b)) => (a - Size::Percent(b.0)).to_pax_value(),
93            (PaxValue::Size(a), PaxValue::Numeric(b))
94            | (PaxValue::Numeric(b), PaxValue::Size(a)) => match a {
95                Size::Pixels(px) => Size::Pixels(px - b).to_pax_value(),
96                Size::Percent(per) => Size::Percent(per - b).to_pax_value(),
97                Size::Combined(px, per) => Size::Combined(px - b, per - b).to_pax_value(),
98            },
99            (PaxValue::Rotation(a), PaxValue::Rotation(b)) => (a + -b).to_pax_value(),
100            (a, b) => {
101                log::warn!("can't subtract {:?} and {:?}", a, b);
102                PaxValue::default()
103            }
104        }
105    }
106}
107
108impl Div for PaxValue {
109    type Output = Self;
110
111    fn div(self, rhs: Self) -> Self::Output {
112        match (self, rhs) {
113            (PaxValue::Numeric(a), PaxValue::Numeric(b)) => (a / b).to_pax_value(),
114            (PaxValue::Size(a), PaxValue::Numeric(b))
115            | (PaxValue::Numeric(b), PaxValue::Size(a)) => match a {
116                Size::Pixels(px) => Size::Pixels(px / b).to_pax_value(),
117                Size::Percent(per) => Size::Percent(per / b).to_pax_value(),
118                Size::Combined(px, per) => Size::Combined(px / b, per / b).to_pax_value(),
119            },
120            (a, b) => {
121                log::warn!("can't divide {:?} and {:?}", a, b);
122                PaxValue::default()
123            }
124        }
125    }
126}
127
128impl Neg for PaxValue {
129    type Output = Self;
130
131    fn neg(self) -> Self::Output {
132        match self {
133            PaxValue::Numeric(a) => (-a).to_pax_value(),
134            PaxValue::Size(a) => (-a).to_pax_value(),
135            PaxValue::Percent(p) => (-p.0).to_pax_value(),
136            PaxValue::Rotation(r) => (-r).to_pax_value(),
137            a => {
138                log::warn!("can't negate {:?}", a);
139                PaxValue::default()
140            }
141        }
142    }
143}
144
145impl PartialOrd for PaxValue {
146    fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
147        match (self, rhs) {
148            (PaxValue::Bool(a), PaxValue::Bool(b)) => a.partial_cmp(b),
149            (PaxValue::Numeric(a), PaxValue::Numeric(b)) => a.partial_cmp(b),
150            (PaxValue::String(a), PaxValue::String(b)) => a.partial_cmp(b),
151            (a, b) => {
152                log::warn!("can't compare {:?} and {:?}", a, b);
153                None
154            }
155        }
156    }
157}
158
159impl Rem for PaxValue {
160    type Output = Self;
161
162    fn rem(self, rhs: Self) -> Self::Output {
163        match (self, rhs) {
164            (PaxValue::Numeric(a), PaxValue::Numeric(b)) => (a % b).to_pax_value(),
165            (a, b) => {
166                log::warn!("can't take remainder of {:?} and {:?}", a, b);
167                PaxValue::default()
168            }
169        }
170    }
171}
172
173impl Not for PaxValue {
174    type Output = PaxValue;
175
176    fn not(self) -> Self::Output {
177        match self {
178            PaxValue::Bool(v) => PaxValue::Bool(!v),
179            v => {
180                log::warn!("can't use not(!) operator on {:?}", v);
181                PaxValue::default()
182            }
183        }
184    }
185}
186
187impl PaxValue {
188    // the operator && currently can't be overloaded, we use this function instead
189    // NOTE: this does not short circuit in the normal way that && does
190    pub fn op_and(self, rhs: Self) -> Self {
191        match (self, rhs) {
192            (PaxValue::Bool(a), PaxValue::Bool(b)) => PaxValue::Bool(a && b),
193            (a, b) => {
194                //panic!("&& operator not valid for {:?} and {:?}", a, b);
195                log::warn!("&& operator not valid for {:?} and {:?}", a, b);
196                PaxValue::default()
197            }
198        }
199    }
200    // the operator || currently can't be overloaded, we use this function instead
201    // NOTE: this does not short circuit in the normal way that && does
202    pub fn op_or(self, rhs: Self) -> Self {
203        match (self, rhs) {
204            (PaxValue::Bool(a), PaxValue::Bool(b)) => PaxValue::Bool(a || b),
205            (a, b) => {
206                //panic!("&& operator not valid for {:?} and {:?}", a, b)
207                log::warn!("&& operator not valid for {:?} and {:?}", a, b);
208                PaxValue::default()
209            }
210        }
211    }
212
213    // Boolean negation used by PAXEL's `!` operator.
214    pub fn op_not(self) -> Self {
215        match self {
216            PaxValue::Bool(v) => PaxValue::Bool(!v),
217            v => {
218                log::warn!("! operator not valid for {:?}", v);
219                PaxValue::default()
220            }
221        }
222    }
223
224    // exponentiation, self ^ exp
225    pub fn pow(self, exp: Self) -> Self {
226        match (self, exp) {
227            (PaxValue::Numeric(a), PaxValue::Numeric(b)) => a.pow(b).to_pax_value(),
228            (a, b) => {
229                log::warn!("exponentiation not valid between {:?} and {:?}", a, b);
230                PaxValue::default()
231            }
232        }
233    }
234
235    // Numeric minimum helper used by PAXEL function dispatch.
236    pub fn min(self, rhs: Self) -> Self {
237        match (self, rhs) {
238            (PaxValue::Numeric(a), PaxValue::Numeric(b)) => a.min(b).to_pax_value(),
239            (a, b) => {
240                log::warn!("min not valid between {:?} and {:?}", a, b);
241                PaxValue::default()
242            }
243        }
244    }
245
246    // Numeric maximum helper used by PAXEL function dispatch.
247    pub fn max(self, rhs: Self) -> Self {
248        match (self, rhs) {
249            (PaxValue::Numeric(a), PaxValue::Numeric(b)) => a.max(b).to_pax_value(),
250            (a, b) => {
251                log::warn!("max not valid between {:?} and {:?}", a, b);
252                PaxValue::default()
253            }
254        }
255    }
256}
257
258//----------------------------PaxAny Arithmetic-----------------------------
259impl Add for PaxAny {
260    type Output = Self;
261
262    fn add(self, rhs: Self) -> Self::Output {
263        match (self, rhs) {
264            (PaxAny::Builtin(a), PaxAny::Builtin(b)) => (a + b).to_pax_any(),
265            _ => panic!("{}", ANY_ARITH_UNSUPPORTED),
266        }
267    }
268}
269
270impl Mul for PaxAny {
271    type Output = Self;
272
273    fn mul(self, rhs: Self) -> Self::Output {
274        match (self, rhs) {
275            (PaxAny::Builtin(a), PaxAny::Builtin(b)) => (a * b).to_pax_any(),
276            _ => panic!("{}", ANY_ARITH_UNSUPPORTED),
277        }
278    }
279}
280
281impl Sub for PaxAny {
282    type Output = Self;
283
284    fn sub(self, rhs: Self) -> Self::Output {
285        match (self, rhs) {
286            (PaxAny::Builtin(a), PaxAny::Builtin(b)) => (a - b).to_pax_any(),
287            _ => panic!("{}", ANY_ARITH_UNSUPPORTED),
288        }
289    }
290}
291
292impl Div for PaxAny {
293    type Output = Self;
294
295    fn div(self, rhs: Self) -> Self::Output {
296        match (self, rhs) {
297            (PaxAny::Builtin(a), PaxAny::Builtin(b)) => (a / b).to_pax_any(),
298            _ => panic!("{}", ANY_ARITH_UNSUPPORTED),
299        }
300    }
301}
302
303impl Rem for PaxAny {
304    type Output = Self;
305
306    fn rem(self, rhs: Self) -> Self::Output {
307        match (self, rhs) {
308            (PaxAny::Builtin(a), PaxAny::Builtin(b)) => (a % b).to_pax_any(),
309            _ => panic!("{}", ANY_ARITH_UNSUPPORTED),
310        }
311    }
312}
313
314impl Neg for PaxAny {
315    type Output = Self;
316
317    fn neg(self) -> Self::Output {
318        match self {
319            PaxAny::Builtin(a) => (-a).to_pax_any(),
320            _ => panic!("{}", ANY_ARITH_UNSUPPORTED),
321        }
322    }
323}
324
325impl PartialEq for PaxAny {
326    fn eq(&self, rhs: &Self) -> bool {
327        match (self, rhs) {
328            (PaxAny::Builtin(a), PaxAny::Builtin(b)) => a == b,
329            _ => panic!("{}", ANY_ARITH_UNSUPPORTED),
330        }
331    }
332}
333
334impl PartialOrd for PaxAny {
335    fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
336        match (self, rhs) {
337            (PaxAny::Builtin(a), PaxAny::Builtin(b)) => a.partial_cmp(b),
338            _ => panic!("{}", ANY_ARITH_UNSUPPORTED),
339        }
340    }
341}
342
343impl Not for PaxAny {
344    type Output = PaxAny;
345
346    fn not(self) -> Self::Output {
347        match self {
348            PaxAny::Builtin(v) => (!v).to_pax_any(),
349            _ => panic!("{}", ANY_ARITH_UNSUPPORTED),
350        }
351    }
352}
353
354impl PaxAny {
355    // the operator && currently can't be overloaded, we use this function instead
356    // NOTE: this does not short circuit in the normal way that && does
357    pub fn op_and(self, rhs: Self) -> Self {
358        match (self, rhs) {
359            (PaxAny::Builtin(a), PaxAny::Builtin(b)) => a.op_and(b).to_pax_any(),
360            _ => panic!("{}", ANY_ARITH_UNSUPPORTED),
361        }
362    }
363    // the operator || currently can't be overloaded, we use this function instead
364    // NOTE: this does not short circuit in the normal way that && does
365    pub fn op_or(self, rhs: Self) -> Self {
366        match (self, rhs) {
367            (PaxAny::Builtin(a), PaxAny::Builtin(b)) => a.op_or(b).to_pax_any(),
368            _ => panic!("{}", ANY_ARITH_UNSUPPORTED),
369        }
370    }
371
372    // exponentiation, self ^ exp
373    pub fn pow(self, exp: Self) -> Self {
374        match (self, exp) {
375            (PaxAny::Builtin(a), PaxAny::Builtin(b)) => a.pow(b).to_pax_any(),
376            _ => panic!("{}", ANY_ARITH_UNSUPPORTED),
377        }
378    }
379}
380
381#[cfg(test)]
382mod tests {
383    use super::*;
384    use crate::Rotation;
385
386    fn deg(value: f64) -> PaxValue {
387        PaxValue::Rotation(Rotation::Degrees(value.into()))
388    }
389
390    fn assert_degrees(value: PaxValue, expected: f64) {
391        let PaxValue::Rotation(rotation) = value else {
392            panic!("expected rotation, got {:?}", value);
393        };
394        assert!((rotation.get_as_degrees() - expected).abs() < 0.0001);
395    }
396
397    #[test]
398    fn pax_value_adds_rotations() {
399        assert_degrees(deg(2.0) + deg(3.0), 5.0);
400    }
401
402    #[test]
403    fn pax_value_subtracts_rotations() {
404        assert_degrees(deg(2.0) - deg(3.5), -1.5);
405    }
406}