use crate::{
Error, TulispContext, TulispObject, builtin::functions::functions::reduce_with,
destruct_eval_bind,
};
macro_rules! compare_impl {
($name:ident, $symbol:literal) => {
fn $name(ctx: &mut TulispContext, args: &TulispObject) -> Result<TulispObject, Error> {
if args.null() || args.cdr_and_then(|x| Ok(x.null()))? {
return Err(Error::out_of_range(format!(
"{} requires at least 2 arguments",
$symbol
)));
}
args.car_and_then(|x| {
ctx.eval_and_then(x, |ctx, first| {
let first = first.as_number()?;
args.cadr_and_then(|x| {
ctx.eval_and_then(x, |ctx, second| {
let second = second.as_number()?;
if std::cmp::PartialOrd::$name(&first, &second) {
args.cdr_and_then(|cdr| {
if cdr.cdr_and_then(|x| Ok(x.null()))? {
Ok(TulispObject::t())
} else {
$name(ctx, &cdr)
}
})
} else {
Ok(TulispObject::nil())
}
})
})
})
})
}
};
}
pub(crate) fn add(ctx: &mut TulispContext) {
compare_impl!(gt, ">");
ctx.add_special_form(">", gt);
compare_impl!(ge, ">=");
ctx.add_special_form(">=", ge);
compare_impl!(lt, "<");
ctx.add_special_form("<", lt);
compare_impl!(le, "<=");
ctx.add_special_form("<=", le);
fn max(ctx: &mut TulispContext, rest: &TulispObject) -> Result<TulispObject, Error> {
reduce_with(ctx, rest, |a, b| Ok(if a > b { a } else { b }))
}
ctx.add_special_form("max", max);
fn min(ctx: &mut TulispContext, rest: &TulispObject) -> Result<TulispObject, Error> {
reduce_with(ctx, rest, |a, b| Ok(if a < b { a } else { b }))
}
ctx.add_special_form("min", min);
ctx.add_special_form("abs", |ctx, args| {
destruct_eval_bind!(ctx, (number) = args);
Ok(number.try_float()?.abs().into())
});
}
#[cfg(test)]
mod tests {
use crate::{TulispContext, test_utils::eval_assert_equal};
#[test]
fn test_abs() {
let ctx = &mut TulispContext::new();
eval_assert_equal(ctx, "(abs -4.0)", "4.0");
eval_assert_equal(ctx, "(abs 0.0)", "0.0");
eval_assert_equal(ctx, "(abs 2.25)", "2.25");
eval_assert_equal(ctx, "(abs -3)", "3.0");
eval_assert_equal(ctx, "(abs 0)", "0.0");
eval_assert_equal(ctx, "(abs 5)", "5.0");
}
}