use crate::{Error, Number, Rest, TulispContext};
pub(crate) fn add(ctx: &mut TulispContext) {
ctx.defun("+", |args: Rest<Number>| -> Result<Number, Error> {
args.into_iter()
.try_fold(Number::Int(0), Number::checked_add)
});
ctx.defun("-", |args: Rest<Number>| -> Result<Number, Error> {
let mut args = args.into_iter();
let Some(first) = args.next() else {
return Ok(Number::Int(0));
};
let rest: Vec<Number> = args.collect();
if rest.is_empty() {
Number::Int(0).checked_sub(first)
} else {
rest.into_iter().try_fold(first, Number::checked_sub)
}
});
ctx.defun("*", |args: Rest<Number>| -> Result<Number, Error> {
args.into_iter()
.try_fold(Number::Int(1), Number::checked_mul)
});
ctx.defun(
"/",
|first: Number, rest: Rest<Number>| -> Result<Number, Error> {
let rest: Vec<Number> = rest.into_iter().collect();
if rest.is_empty() {
Number::Int(1).checked_div(first)
} else {
rest.into_iter().try_fold(first, Number::checked_div)
}
},
);
ctx.defun("1+", |a: Number| -> Result<Number, Error> {
a.checked_add(Number::Int(1))
});
ctx.defun("1-", |a: Number| -> Result<Number, Error> {
a.checked_sub(Number::Int(1))
});
ctx.defun("mod", |a: Number, b: Number| -> Result<Number, Error> {
a.checked_mod(b)
});
ctx.defun("%", |a: i64, b: i64| -> Result<i64, Error> {
if b == 0 {
return Err(Error::out_of_range("Division by zero".to_string()));
}
Ok(a.wrapping_rem(b))
});
}
#[cfg(test)]
mod tests {
use crate::TulispContext;
use crate::test_utils::{eval_assert, eval_assert_equal, eval_assert_error};
#[test]
fn percent_is_truncated_integer_remainder() {
let mut ctx = TulispContext::new();
eval_assert_equal(&mut ctx, "(% 7 3)", "1");
eval_assert_equal(&mut ctx, "(% -7 3)", "-1");
eval_assert_equal(&mut ctx, "(% 7 -3)", "1");
eval_assert_equal(&mut ctx, "(% -7 -3)", "-1");
eval_assert_equal(&mut ctx, "(% -9223372036854775808 -1)", "0");
}
#[test]
fn percent_by_zero_errors() {
let mut ctx = TulispContext::new();
eval_assert_error(
&mut ctx,
"(% 5 0)",
"ERR OutOfRange: Division by zero\n<eval_string>:1.1-1.7: at (% 5 0)\n",
);
}
#[test]
fn nullary_arithmetic_uses_identity() {
let mut ctx = TulispContext::new();
eval_assert_equal(&mut ctx, "(+)", "0");
eval_assert_equal(&mut ctx, "(*)", "1");
eval_assert_equal(&mut ctx, "(-)", "0");
eval_assert_equal(&mut ctx, "(- 5)", "-5");
eval_assert_equal(&mut ctx, "(+ 1 2 3)", "6");
eval_assert_equal(&mut ctx, "(* 2 3 4)", "24");
eval_assert_equal(&mut ctx, "(- 10 1 2)", "7");
}
#[test]
fn division() {
let mut ctx = TulispContext::new();
eval_assert_equal(&mut ctx, "(/ 0 10)", "0");
eval_assert_equal(&mut ctx, "(/ 24 2 2)", "6");
eval_assert(&mut ctx, "(integerp (/ 10 3))");
eval_assert(&mut ctx, "(integerp (/ 7 2 2))");
eval_assert_equal(&mut ctx, "(/ 10 3)", "3");
eval_assert(&mut ctx, "(floatp (/ 10 3.0))");
eval_assert(&mut ctx, "(floatp (/ 10.0 3))");
eval_assert_equal(&mut ctx, "(/ 1)", "1");
eval_assert_equal(&mut ctx, "(/ 10)", "0");
eval_assert_equal(&mut ctx, "(/ 10.0)", "0.1");
eval_assert(&mut ctx, "(numberp (/ 1.0 0.0))");
eval_assert(&mut ctx, "(numberp (/ 1.0 0))");
eval_assert(&mut ctx, "(numberp (/ 1 0.0))");
eval_assert(&mut ctx, "(numberp (funcall '/ 1.0 0.0))");
}
#[test]
fn division_by_zero_errors() {
let mut ctx = TulispContext::new();
eval_assert_error(
&mut ctx,
"(/ 10 0)",
"ERR OutOfRange: Division by zero\n<eval_string>:1.1-1.8: at (/ 10 0)\n",
);
eval_assert_error(
&mut ctx,
"(let ((a 10) (b 0)) (/ a b))",
"ERR OutOfRange: Division by zero\n<eval_string>:1.21-1.27: at (/ a b)\n\
<eval_string>:1.1-1.28: at (let ((a 10) (b 0)) (/ a b))\n",
);
eval_assert_error(
&mut ctx,
"(funcall '/ 1 0)",
"ERR OutOfRange: Division by zero\n<eval_string>:1.1-1.16: at (funcall '/ 1 0)\n",
);
}
#[test]
fn division_overflow_errors_rather_than_panicking() {
let mut ctx = TulispContext::new();
eval_assert_equal(
&mut ctx,
"(condition-case nil (/ -9223372036854775808 -1) (error 'caught))",
"'caught",
);
}
#[test]
fn floored_modulo() {
let mut ctx = TulispContext::new();
eval_assert_equal(&mut ctx, "(mod 32 5)", "2");
eval_assert_equal(&mut ctx, "(mod -7 3)", "2");
eval_assert_equal(&mut ctx, "(mod 7 -3)", "-2");
eval_assert_equal(&mut ctx, "(mod -7 -3)", "-1");
eval_assert_equal(&mut ctx, "(mod -7.0 3)", "2.0");
eval_assert_equal(&mut ctx, "(mod 7.0 -3)", "-2.0");
eval_assert(&mut ctx, "(numberp (mod 5.0 0))");
eval_assert_equal(&mut ctx, "(mod -9223372036854775808 -1)", "0");
}
#[test]
fn modulo_by_zero_errors() {
let mut ctx = TulispContext::new();
eval_assert_error(
&mut ctx,
"(mod 5 0)",
"ERR OutOfRange: Division by zero\n<eval_string>:1.1-1.9: at (mod 5 0)\n",
);
}
}