1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
#![allow(clippy::should_implement_trait)] // TODO

use {
	super::ValueCore,
	crate::{Convert, ConvertFrom, Result, Value, ValueError},
};

// These were using references, they now consume their variables. See ::recipe.
macro_rules! natural_binary_op {
    ($name: ident, $trait: ident, $op: tt) => {
        pub fn $name<Core>(self, other: Self) -> Result<Self>
        where
            Core: ValueCore + $trait<Output = Core>,
        {
            let (left, right) = (Core::convert_from(self)?, Core::convert_from(other)?);
            let result = left $op right;
            Ok(result.into())
        }
    };
}
macro_rules! natural_binary_ops {
    ($(($name: ident, $trait: ident, $op: tt, $generic_name: ident)),+) => {
        use std::ops::{$($trait),+};
        impl Value {
            $(
                natural_binary_op!($name, $trait, $op);
                generic!($name, $generic_name);
            )+
        }
    }
}

macro_rules! boolean_binary_op {
    ($name: ident, $op: tt) => {
        pub fn $name(self, other: Self) -> Result<Self>
        {
            let (left, right): (bool, bool) = (self.convert()?, other.convert()?);
            let result = left $op right;
            Ok(result.into())
        }
    };
}
macro_rules! boolean_binary_ops {
    ($(($name: ident, $op: tt)),+) => {
        impl Value {
            $(boolean_binary_op!($name, $op);)+
        }
    }
}

macro_rules! comparative_binary_op {
    ($name: ident, $op: tt) => {
        pub fn $name(self, other: Self) -> Result<Self> {
            Ok(Value::Bool(self $op other))
        }
    };
}
macro_rules! comparative_binary_ops {
    ($(($name: ident, $op: tt)),+) => {
        impl Value {
            $(comparative_binary_op!($name, $op);)+
        }
    }
}

macro_rules! generic {
	($name: ident, $generic_name: ident) => {
		pub fn $generic_name(self, other: Self) -> Result<Self> {
			if matches!(self, Value::Null) || matches!(other, Value::Null) {
				Ok(Value::Null)
			} else if !i64::convert_from(self.clone()).is_err()
				&& !i64::convert_from(other.clone()).is_err()
			{
				self.$name::<i64>(other)
			} else if !f64::convert_from(self.clone()).is_err()
				&& !f64::convert_from(other.clone()).is_err()
			{
				self.$name::<f64>(other)
			} else {
				Err(ValueError::OnlySupportsNumeric(
					if f64::convert_from(self.clone()).is_err() {
						self
					} else {
						other
					},
					stringify!($name),
				)
				.into())
			}
		}
	};
}

natural_binary_ops!(
	(add, Add, +, generic_add),
	(subtract, Sub, -, generic_subtract),
	(multiply, Mul, *, generic_multiply),
	(divide, Div, /, generic_divide),
	(modulus, Rem, %, generic_modulus)
);

boolean_binary_ops!(
	(and, &),
	(or, |),
	(xor, ^)
);

comparative_binary_ops!(
	(eq, ==),
	(not_eq, !=),
	(gt, >),
	(gt_eq, >=),
	(lt, <),
	(lt_eq, <=)
);

impl Value {
	pub fn string_concat(self, other: Self) -> Result<Self> {
		Ok(format!(
			"{}{}",
			String::convert_from(self)?,
			String::convert_from(other)?
		)
		.into())
	}
}