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 repetition_overflow(count: &BigInt) -> DogeError {
21 DogeError::overflow(format!(
22 "repeat count {count} produces a sequence that is too large"
23 ))
24}
25
26fn repetition_shape(unit_len: usize, count: &BigInt) -> DogeResult<(usize, usize)> {
27 let copies = if count.is_negative() {
28 0
29 } else {
30 count.to_usize().ok_or_else(|| repetition_overflow(count))?
31 };
32 let total_len = unit_len
33 .checked_mul(copies)
34 .ok_or_else(|| repetition_overflow(count))?;
35 Ok((copies, total_len))
36}
37
38fn mix_float_decimal(sym: &str, a: &Value, b: &Value) -> DogeError {
42 DogeError::type_error(format!(
43 "cannot {sym} {} and {} — Decimal is exact and Float is not; convert one with dec() or float()",
44 a.describe(),
45 b.describe()
46 ))
47}
48
49fn numeric_binop(
53 sym: &str,
54 a: &Value,
55 b: &Value,
56 dec_op: impl Fn(BigDecimal, BigDecimal) -> BigDecimal,
57 float_op: impl Fn(f64, f64) -> f64,
58) -> DogeResult {
59 if is_decimal(a) || is_decimal(b) {
60 if is_float(a) || is_float(b) {
61 return Err(mix_float_decimal(sym, a, b));
62 }
63 return match (as_decimal(a), as_decimal(b)) {
64 (Some(x), Some(y)) => Ok(Value::decimal(dec_op(x, y))),
65 _ => Err(type_err_binop(sym, a, b)),
66 };
67 }
68 match (as_f64(a), as_f64(b)) {
69 (Some(x), Some(y)) => Ok(Value::Float(float_op(x, y))),
70 _ => Err(type_err_binop(sym, a, b)),
71 }
72}
73
74fn bigint_floordiv(x: &BigInt, y: &BigInt) -> BigInt {
77 let q = x / y;
78 let r = x % y;
79 if !r.is_zero() && (r.is_negative() != y.is_negative()) {
80 q - BigInt::from(1)
81 } else {
82 q
83 }
84}
85
86fn decimal_floordiv(x: &BigDecimal, y: &BigDecimal) -> BigDecimal {
88 (x / y).with_scale_round(0, RoundingMode::Floor)
89}
90
91pub fn add(a: Value, b: Value) -> DogeResult {
94 match (&a, &b) {
95 (Value::Int(x), Value::Int(y)) => Ok(Value::int(x + y)),
96 (Value::Str(x), Value::Str(y)) => Ok(Value::str(format!("{x}{y}"))),
97 (Value::Bytes(x), Value::Bytes(y)) => {
98 let mut joined = Vec::with_capacity(x.len() + y.len());
99 joined.extend_from_slice(x);
100 joined.extend_from_slice(y);
101 Ok(Value::bytes(joined))
102 }
103 (Value::Str(x), Value::Error(e)) => Ok(Value::str(format!("{x}{}", e.message))),
107 (Value::Error(e), Value::Str(y)) => Ok(Value::str(format!("{}{y}", e.message))),
108 (Value::List(x), Value::List(y)) => {
109 let mut joined = x.borrow().clone();
110 joined.extend(y.borrow().iter().cloned());
111 Ok(Value::list(joined))
112 }
113 _ => numeric_binop("+", &a, &b, |x, y| x + y, |x, y| x + y),
114 }
115}
116
117pub fn sub(a: Value, b: Value) -> DogeResult {
119 if let (Value::Int(x), Value::Int(y)) = (&a, &b) {
120 return Ok(Value::int(x - y));
121 }
122 numeric_binop("-", &a, &b, |x, y| x - y, |x, y| x - y)
123}
124
125pub fn mul(a: Value, b: Value) -> DogeResult {
128 match (&a, &b) {
129 (Value::Int(x), Value::Int(y)) => return Ok(Value::int(x * y)),
130 (Value::Str(text), Value::Int(count)) | (Value::Int(count), Value::Str(text)) => {
131 let (copies, total_len) = repetition_shape(text.len(), count)?;
132 let mut repeated = String::new();
133 repeated
134 .try_reserve_exact(total_len)
135 .map_err(|_| repetition_overflow(count))?;
136 for _ in 0..copies {
137 repeated.push_str(text);
138 }
139 return Ok(Value::str(repeated));
140 }
141 (Value::List(items), Value::Int(count)) | (Value::Int(count), Value::List(items)) => {
142 let items = items.borrow();
143 let (copies, total_len) = repetition_shape(items.len(), count)?;
144 let mut repeated = Vec::new();
145 repeated
146 .try_reserve_exact(total_len)
147 .map_err(|_| repetition_overflow(count))?;
148 for _ in 0..copies {
149 repeated.extend(items.iter().cloned());
150 }
151 return Ok(Value::list(repeated));
152 }
153 _ => {}
154 }
155 numeric_binop("*", &a, &b, |x, y| x * y, |x, y| x * y)
156}
157
158pub fn div(a: Value, b: Value) -> DogeResult {
161 if is_decimal(&a) || is_decimal(&b) {
162 if is_float(&a) || is_float(&b) {
163 return Err(mix_float_decimal("/", &a, &b));
164 }
165 return match (as_decimal(&a), as_decimal(&b)) {
166 (Some(_), Some(y)) if y.is_zero() => Err(div_by_zero("/")),
167 (Some(x), Some(y)) => Ok(Value::decimal(x / y)),
168 _ => Err(type_err_binop("/", &a, &b)),
169 };
170 }
171 match (as_f64(&a), as_f64(&b)) {
172 (Some(_), Some(0.0)) => Err(div_by_zero("/")),
173 (Some(x), Some(y)) => Ok(Value::Float(x / y)),
174 _ => Err(type_err_binop("/", &a, &b)),
175 }
176}
177
178pub fn floordiv(a: Value, b: Value) -> DogeResult {
181 match (&a, &b) {
182 (Value::Int(x), Value::Int(y)) => {
183 if y.is_zero() {
184 return Err(div_by_zero("//"));
185 }
186 Ok(Value::int(bigint_floordiv(x, y)))
187 }
188 _ if is_decimal(&a) || is_decimal(&b) => {
189 if is_float(&a) || is_float(&b) {
190 return Err(mix_float_decimal("//", &a, &b));
191 }
192 match (as_decimal(&a), as_decimal(&b)) {
193 (Some(_), Some(y)) if y.is_zero() => Err(div_by_zero("//")),
194 (Some(x), Some(y)) => Ok(Value::decimal(decimal_floordiv(&x, &y))),
195 _ => Err(type_err_binop("//", &a, &b)),
196 }
197 }
198 _ => match (as_f64(&a), as_f64(&b)) {
199 (Some(_), Some(0.0)) => Err(div_by_zero("//")),
200 (Some(x), Some(y)) => Ok(Value::Float((x / y).floor())),
201 _ => Err(type_err_binop("//", &a, &b)),
202 },
203 }
204}
205
206pub fn rem(a: Value, b: Value) -> DogeResult {
209 match (&a, &b) {
210 (Value::Int(x), Value::Int(y)) => {
211 if y.is_zero() {
212 return Err(div_by_zero("%"));
213 }
214 let r = x % y;
215 let m = if !r.is_zero() && (r.is_negative() != y.is_negative()) {
216 r + y
217 } else {
218 r
219 };
220 Ok(Value::int(m))
221 }
222 _ if is_decimal(&a) || is_decimal(&b) => {
223 if is_float(&a) || is_float(&b) {
224 return Err(mix_float_decimal("%", &a, &b));
225 }
226 match (as_decimal(&a), as_decimal(&b)) {
227 (Some(_), Some(y)) if y.is_zero() => Err(div_by_zero("%")),
228 (Some(x), Some(y)) => {
229 let f = decimal_floordiv(&x, &y);
230 Ok(Value::decimal(&x - f * &y))
231 }
232 _ => Err(type_err_binop("%", &a, &b)),
233 }
234 }
235 _ => match (as_f64(&a), as_f64(&b)) {
236 (Some(_), Some(0.0)) => Err(div_by_zero("%")),
237 (Some(x), Some(y)) => {
238 let r = x % y;
239 let m = if r != 0.0 && ((r < 0.0) != (y < 0.0)) {
240 r + y
241 } else {
242 r
243 };
244 Ok(Value::Float(m))
245 }
246 _ => Err(type_err_binop("%", &a, &b)),
247 },
248 }
249}
250
251pub fn pow(a: Value, b: Value) -> DogeResult {
257 match (&a, &b) {
258 (Value::Int(base), Value::Int(exp)) if !exp.is_negative() => {
259 let e = exp
260 .to_u32()
261 .ok_or_else(|| DogeError::overflow(format!("exponent {exp} is too large")))?;
262 Ok(Value::int(Pow::pow(base.clone(), e)))
263 }
264 (Value::Int(base), Value::Int(_)) if base.is_zero() => Err(div_by_zero("**")),
267 (Value::Decimal(base), Value::Int(exp)) if !exp.is_negative() => {
268 let e = exp
269 .to_u32()
270 .ok_or_else(|| DogeError::overflow(format!("exponent {exp} is too large")))?;
271 let mut result = BigDecimal::from(1);
272 for _ in 0..e {
273 result *= base;
274 }
275 Ok(Value::decimal(result))
276 }
277 _ if is_decimal(&a) || is_decimal(&b) => Err(DogeError::type_error(format!(
278 "cannot raise {} to {} — a Decimal power needs a non-negative Int exponent",
279 a.describe(),
280 b.describe()
281 ))),
282 _ => match (as_f64(&a), as_f64(&b)) {
283 (Some(x), Some(y)) if x == 0.0 && y < 0.0 => Err(div_by_zero("**")),
284 (Some(x), Some(y)) => Ok(Value::Float(x.powf(y))),
285 _ => Err(type_err_binop("**", &a, &b)),
286 },
287 }
288}
289
290pub fn neg(a: Value) -> DogeResult {
292 match &a {
293 Value::Int(n) => Ok(Value::int(-n)),
294 Value::Float(f) => Ok(Value::Float(-f)),
295 Value::Decimal(d) => Ok(Value::decimal(-d)),
296 _ => Err(DogeError::type_error(format!(
297 "cannot negate {}",
298 a.describe()
299 ))),
300 }
301}
302
303pub fn not_(a: Value) -> DogeResult {
305 Ok(Value::Bool(!a.truthy()))
306}