1use bigdecimal::{BigDecimal, Pow, RoundingMode, Signed, ToPrimitive, Zero};
2use num_bigint::BigInt;
3
4use super::{as_decimal, as_f64, is_decimal, is_float};
5use crate::error::{DogeError, DogeResult};
6use crate::value::Value;
7
8fn type_err_binop(sym: &str, a: &Value, b: &Value) -> DogeError {
9 DogeError::type_error(format!(
10 "cannot {sym} {} and {}",
11 a.describe(),
12 b.describe()
13 ))
14}
15
16fn div_by_zero(sym: &str) -> DogeError {
17 DogeError::division_by_zero(format!("cannot {sym} by zero"))
18}
19
20fn mix_float_decimal(sym: &str, a: &Value, b: &Value) -> DogeError {
24 DogeError::type_error(format!(
25 "cannot {sym} {} and {} — Decimal is exact and Float is not; convert one with dec() or float()",
26 a.describe(),
27 b.describe()
28 ))
29}
30
31fn numeric_binop(
35 sym: &str,
36 a: &Value,
37 b: &Value,
38 dec_op: impl Fn(BigDecimal, BigDecimal) -> BigDecimal,
39 float_op: impl Fn(f64, f64) -> f64,
40) -> DogeResult {
41 if is_decimal(a) || is_decimal(b) {
42 if is_float(a) || is_float(b) {
43 return Err(mix_float_decimal(sym, a, b));
44 }
45 return match (as_decimal(a), as_decimal(b)) {
46 (Some(x), Some(y)) => Ok(Value::decimal(dec_op(x, y))),
47 _ => Err(type_err_binop(sym, a, b)),
48 };
49 }
50 match (as_f64(a), as_f64(b)) {
51 (Some(x), Some(y)) => Ok(Value::Float(float_op(x, y))),
52 _ => Err(type_err_binop(sym, a, b)),
53 }
54}
55
56fn bigint_floordiv(x: &BigInt, y: &BigInt) -> BigInt {
59 let q = x / y;
60 let r = x % y;
61 if !r.is_zero() && (r.is_negative() != y.is_negative()) {
62 q - BigInt::from(1)
63 } else {
64 q
65 }
66}
67
68fn decimal_floordiv(x: &BigDecimal, y: &BigDecimal) -> BigDecimal {
70 (x / y).with_scale_round(0, RoundingMode::Floor)
71}
72
73pub fn add(a: Value, b: Value) -> DogeResult {
76 match (&a, &b) {
77 (Value::Int(x), Value::Int(y)) => Ok(Value::int(x + y)),
78 (Value::Str(x), Value::Str(y)) => Ok(Value::str(format!("{x}{y}"))),
79 (Value::Bytes(x), Value::Bytes(y)) => {
80 let mut joined = Vec::with_capacity(x.len() + y.len());
81 joined.extend_from_slice(x);
82 joined.extend_from_slice(y);
83 Ok(Value::bytes(joined))
84 }
85 (Value::Str(x), Value::Error(e)) => Ok(Value::str(format!("{x}{}", e.message))),
89 (Value::Error(e), Value::Str(y)) => Ok(Value::str(format!("{}{y}", e.message))),
90 (Value::List(x), Value::List(y)) => {
91 let mut joined = x.borrow().clone();
92 joined.extend(y.borrow().iter().cloned());
93 Ok(Value::list(joined))
94 }
95 _ => numeric_binop("+", &a, &b, |x, y| x + y, |x, y| x + y),
96 }
97}
98
99pub fn sub(a: Value, b: Value) -> DogeResult {
101 if let (Value::Int(x), Value::Int(y)) = (&a, &b) {
102 return Ok(Value::int(x - y));
103 }
104 numeric_binop("-", &a, &b, |x, y| x - y, |x, y| x - y)
105}
106
107pub fn mul(a: Value, b: Value) -> DogeResult {
109 if let (Value::Int(x), Value::Int(y)) = (&a, &b) {
110 return Ok(Value::int(x * y));
111 }
112 numeric_binop("*", &a, &b, |x, y| x * y, |x, y| x * y)
113}
114
115pub fn div(a: Value, b: Value) -> DogeResult {
118 if is_decimal(&a) || is_decimal(&b) {
119 if is_float(&a) || is_float(&b) {
120 return Err(mix_float_decimal("/", &a, &b));
121 }
122 return match (as_decimal(&a), as_decimal(&b)) {
123 (Some(_), Some(y)) if y.is_zero() => Err(div_by_zero("/")),
124 (Some(x), Some(y)) => Ok(Value::decimal(x / y)),
125 _ => Err(type_err_binop("/", &a, &b)),
126 };
127 }
128 match (as_f64(&a), as_f64(&b)) {
129 (Some(_), Some(0.0)) => Err(div_by_zero("/")),
130 (Some(x), Some(y)) => Ok(Value::Float(x / y)),
131 _ => Err(type_err_binop("/", &a, &b)),
132 }
133}
134
135pub fn floordiv(a: Value, b: Value) -> DogeResult {
138 match (&a, &b) {
139 (Value::Int(x), Value::Int(y)) => {
140 if y.is_zero() {
141 return Err(div_by_zero("//"));
142 }
143 Ok(Value::int(bigint_floordiv(x, y)))
144 }
145 _ if is_decimal(&a) || is_decimal(&b) => {
146 if is_float(&a) || is_float(&b) {
147 return Err(mix_float_decimal("//", &a, &b));
148 }
149 match (as_decimal(&a), as_decimal(&b)) {
150 (Some(_), Some(y)) if y.is_zero() => Err(div_by_zero("//")),
151 (Some(x), Some(y)) => Ok(Value::decimal(decimal_floordiv(&x, &y))),
152 _ => Err(type_err_binop("//", &a, &b)),
153 }
154 }
155 _ => match (as_f64(&a), as_f64(&b)) {
156 (Some(_), Some(0.0)) => Err(div_by_zero("//")),
157 (Some(x), Some(y)) => Ok(Value::Float((x / y).floor())),
158 _ => Err(type_err_binop("//", &a, &b)),
159 },
160 }
161}
162
163pub fn rem(a: Value, b: Value) -> DogeResult {
166 match (&a, &b) {
167 (Value::Int(x), Value::Int(y)) => {
168 if y.is_zero() {
169 return Err(div_by_zero("%"));
170 }
171 let r = x % y;
172 let m = if !r.is_zero() && (r.is_negative() != y.is_negative()) {
173 r + y
174 } else {
175 r
176 };
177 Ok(Value::int(m))
178 }
179 _ if is_decimal(&a) || is_decimal(&b) => {
180 if is_float(&a) || is_float(&b) {
181 return Err(mix_float_decimal("%", &a, &b));
182 }
183 match (as_decimal(&a), as_decimal(&b)) {
184 (Some(_), Some(y)) if y.is_zero() => Err(div_by_zero("%")),
185 (Some(x), Some(y)) => {
186 let f = decimal_floordiv(&x, &y);
187 Ok(Value::decimal(&x - f * &y))
188 }
189 _ => Err(type_err_binop("%", &a, &b)),
190 }
191 }
192 _ => match (as_f64(&a), as_f64(&b)) {
193 (Some(_), Some(0.0)) => Err(div_by_zero("%")),
194 (Some(x), Some(y)) => {
195 let r = x % y;
196 let m = if r != 0.0 && ((r < 0.0) != (y < 0.0)) {
197 r + y
198 } else {
199 r
200 };
201 Ok(Value::Float(m))
202 }
203 _ => Err(type_err_binop("%", &a, &b)),
204 },
205 }
206}
207
208pub fn pow(a: Value, b: Value) -> DogeResult {
214 match (&a, &b) {
215 (Value::Int(base), Value::Int(exp)) if !exp.is_negative() => {
216 let e = exp
217 .to_u32()
218 .ok_or_else(|| DogeError::overflow(format!("exponent {exp} is too large")))?;
219 Ok(Value::int(Pow::pow(base.clone(), e)))
220 }
221 (Value::Int(base), Value::Int(_)) if base.is_zero() => Err(div_by_zero("**")),
224 (Value::Decimal(base), Value::Int(exp)) if !exp.is_negative() => {
225 let e = exp
226 .to_u32()
227 .ok_or_else(|| DogeError::overflow(format!("exponent {exp} is too large")))?;
228 let mut result = BigDecimal::from(1);
229 for _ in 0..e {
230 result *= base;
231 }
232 Ok(Value::decimal(result))
233 }
234 _ if is_decimal(&a) || is_decimal(&b) => Err(DogeError::type_error(format!(
235 "cannot raise {} to {} — a Decimal power needs a non-negative Int exponent",
236 a.describe(),
237 b.describe()
238 ))),
239 _ => match (as_f64(&a), as_f64(&b)) {
240 (Some(x), Some(y)) if x == 0.0 && y < 0.0 => Err(div_by_zero("**")),
241 (Some(x), Some(y)) => Ok(Value::Float(x.powf(y))),
242 _ => Err(type_err_binop("**", &a, &b)),
243 },
244 }
245}
246
247pub fn neg(a: Value) -> DogeResult {
249 match &a {
250 Value::Int(n) => Ok(Value::int(-n)),
251 Value::Float(f) => Ok(Value::Float(-f)),
252 Value::Decimal(d) => Ok(Value::decimal(-d)),
253 _ => Err(DogeError::type_error(format!(
254 "cannot negate {}",
255 a.describe()
256 ))),
257 }
258}
259
260pub fn not_(a: Value) -> DogeResult {
262 Ok(Value::Bool(!a.truthy()))
263}