124 lines
2.9 KiB
Rust
124 lines
2.9 KiB
Rust
use crate::eval::{err, RTError};
|
|
use crate::value::Value;
|
|
|
|
crate type Func = fn(&[Value]) -> Result<Value, RTError>;
|
|
|
|
crate fn prims() -> &'static [(&'static str, Func)] {
|
|
&[
|
|
("*", mul),
|
|
("+", add),
|
|
("-", sub),
|
|
("/", div),
|
|
// TODO: Spec compiance
|
|
("=", equals),
|
|
("eq?", equals),
|
|
("eqv?", equals),
|
|
("equal?", equals),
|
|
("display", display),
|
|
("newline", newline),
|
|
("abs", abs),
|
|
("<", lt),
|
|
(">", gt),
|
|
("<=", le),
|
|
(">=", ge),
|
|
]
|
|
}
|
|
|
|
// TODO: DRY +-/*
|
|
fn add(args: &[Value]) -> Result<Value, RTError> {
|
|
args.iter()
|
|
.map(Value::as_num)
|
|
.try_fold(0.0, |a, b| Ok(a + b?))
|
|
.map(Value::Num)
|
|
}
|
|
|
|
fn mul(args: &[Value]) -> Result<Value, RTError> {
|
|
args.iter()
|
|
.map(Value::as_num)
|
|
.try_fold(1.0, |a, b| Ok(a * b?))
|
|
.map(Value::Num)
|
|
}
|
|
|
|
fn div(args: &[Value]) -> Result<Value, RTError> {
|
|
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<Value, RTError> {
|
|
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<Value, RTError> {
|
|
Ok(Value::Bool(args.array_windows().all(|[l, r]| l == r)))
|
|
}
|
|
|
|
fn display(args: &[Value]) -> Result<Value, RTError> {
|
|
let [arg] = args else {return err("To many args to `display`".to_owned())};
|
|
print!("{:?}", arg);
|
|
Ok(Value::Trap)
|
|
}
|
|
|
|
fn newline(args: &[Value]) -> Result<Value, RTError> {
|
|
if !args.is_empty() {
|
|
return err("Newline takes no args".to_owned());
|
|
}
|
|
println!();
|
|
Ok(Value::Trap)
|
|
}
|
|
|
|
fn abs(args: &[Value]) -> Result<Value, RTError> {
|
|
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<Value, RTError> {
|
|
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<Value, RTError> {
|
|
compare_core(args, |l, r| l $op r, stringify!($op))
|
|
}
|
|
)*
|
|
};
|
|
}
|
|
|
|
cmps! { (lt <) (gt >) (le <=) (ge >=) } |