handball/src/prims.rs

134 lines
3.0 KiB
Rust

use crate::eval::{err, RTError};
use crate::value::Value;
type Result = std::result::Result<Value, RTError>;
crate type NativeFunc = for<'a> fn(&'a [Value]) -> Result;
crate fn prims() -> &'static [(&'static str, NativeFunc)] {
&[
("*", mul),
("+", add),
("-", sub),
("/", div),
// TODO: Spec compiance
("=", equals),
("eq?", equals),
("eqv?", equals),
("equal?", equals),
("display", display),
("newline", newline),
("abs", abs),
("<", lt),
(">", gt),
("<=", le),
(">=", ge),
("_Z_debug", z_debug)
]
}
// TODO: DRY +-/*
fn add(args: &[Value]) -> Result {
args.iter()
.map(Value::as_num)
.try_fold(0.0, |a, b| Ok(a + b?))
.map(Value::Num)
}
fn mul(args: &[Value]) -> Result {
args.iter()
.map(Value::as_num)
.try_fold(1.0, |a, b| Ok(a * b?))
.map(Value::Num)
}
fn div(args: &[Value]) -> Result {
let init = args
.get(0)
.ok_or_else(|| RTError("`div` needs at least one argument".to_owned()))?
.as_num()?;
Ok(Value::Num(if args.len() == 1 {
1.0 / init
} else {
let rest = mul(&args[1..])?.as_num().unwrap();
init / rest
}))
}
fn sub(args: &[Value]) -> Result {
let init = args
.get(0)
.ok_or_else(|| RTError("`sub` needs at least one argument".to_owned()))?
.as_num()?;
Ok(Value::Num(if args.len() == 1 {
-init
} else {
let rest = add(&args[1..])?.as_num().unwrap();
init - rest
}))
}
fn equals(args: &[Value]) -> Result {
Ok(Value::Bool(args.array_windows().all(|[l, r]| l == r)))
}
fn display(args: &[Value]) -> Result {
let [arg] = args else {return err("To many args to `display`".to_owned())};
print!("{}", arg);
Ok(Value::Trap)
}
fn newline(args: &[Value]) -> Result {
if !args.is_empty() {
return err("Newline takes no args".to_owned());
}
println!();
Ok(Value::Trap)
}
fn abs(args: &[Value]) -> Result {
let [v] = args else { return err("abs takes 1 arg".to_owned()) };
let ans = v.as_num()?.abs();
Ok(Value::Num(ans))
}
crate fn compare_core(
args: &[Value],
f: fn(f64, f64) -> bool,
name: &'static str,
) -> Result {
for [l, r] in args.array_windows() {
let (Value::Num(l), Value::Num(r)) = (l,r) else
{
return err(format!("`{}` args must be numbers", name));
};
// TODO: Ensure this is correct wrt NaN
if !f(*l, *r) {
return Ok(Value::Bool(false));
};
}
Ok(Value::Bool(true))
}
macro_rules! cmps {
($(($name:ident $op:tt))*) => {
$(
fn $name(args: &[Value]) -> Result {
compare_core(args, |l, r| l $op r, stringify!($op))
}
)*
};
}
cmps! { (lt <) (gt >) (le <=) (ge >=) }
fn z_debug(args: &[Value]) -> Result {
eprintln!("{:?}", args);
Ok(Value::Trap)
}