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";
12impl Add for PaxValue {
14 type Output = Self;
15
16 fn add(self, rhs: Self) -> Self::Output {
17 match (self, rhs) {
18 (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 (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 (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 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 log::warn!("&& operator not valid for {:?} and {:?}", a, b);
196 PaxValue::default()
197 }
198 }
199 }
200 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 log::warn!("&& operator not valid for {:?} and {:?}", a, b);
208 PaxValue::default()
209 }
210 }
211 }
212
213 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 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 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 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
258impl 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 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 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 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}