use super::eval::{finite, Arg, Ctx, Library, Reason};
use super::formula::Expr;
use super::value::{ErrorKind, Value};
pub fn language() -> Library {
let mut lib = Library::empty();
lib.register_form("IF", if_);
lib.register("AND", and);
lib.register("OR", or);
lib.register("NOT", not);
lib.register("SUM", sum);
lib.register("MIN", min);
lib.register("MAX", max);
lib.register("ABS", abs);
lib.register("INT", int);
lib.register("MOD", modulo);
lib.register("ROW", row);
lib.register("COLUMN", column);
lib.register_form("CHOOSE", choose);
lib.register("SIN", sin);
lib
}
pub const NAMES: [&str; 14] = [
"IF", "AND", "OR", "NOT", "SUM", "MIN", "MAX", "ABS", "INT", "MOD", "ROW", "COLUMN", "CHOOSE",
"SIN",
];
pub type Numbers = Result<Vec<f64>, ErrorKind>;
pub fn each_number<F: FnMut(f64)>(
ctx: &Ctx<'_>,
args: &[Arg],
mut f: F,
) -> Result<Result<(), ErrorKind>, Reason> {
for a in args {
match a {
Arg::Scalar(v) => match v.number() {
Ok(n) => f(n),
Err(k) => return Ok(Err(k)),
},
Arg::Area { areas, .. } => {
let mut err: Option<ErrorKind> = None;
ctx.each(areas, |v| {
match v {
Value::Number(n) => f(n),
Value::Error(k) => {
err.get_or_insert(k);
}
_ => {}
}
Ok(())
})?;
if let Some(k) = err {
return Ok(Err(k));
}
}
}
}
Ok(Ok(()))
}
pub fn each_truth<F: FnMut(bool)>(
ctx: &Ctx<'_>,
args: &[Arg],
mut f: F,
) -> Result<Result<(), ErrorKind>, Reason> {
for a in args {
match a {
Arg::Scalar(v) => match v.truth() {
Ok(b) => f(b),
Err(k) => return Ok(Err(k)),
},
Arg::Area { areas, .. } => {
let mut err: Option<ErrorKind> = None;
ctx.each(areas, |v| {
match v {
Value::Bool(b) => f(b),
Value::Number(n) => f(n != 0.0),
Value::Error(k) => {
err.get_or_insert(k);
}
_ => {}
}
Ok(())
})?;
if let Some(k) = err {
return Ok(Err(k));
}
}
}
}
Ok(Ok(()))
}
pub fn numbers(ctx: &Ctx<'_>, args: &[Arg]) -> Result<Numbers, Reason> {
let mut out = Vec::new();
let walked = each_number(ctx, args, |n| out.push(n))?;
Ok(walked.map(|()| out))
}
pub fn truths(ctx: &Ctx<'_>, args: &[Arg]) -> Result<Result<Vec<bool>, ErrorKind>, Reason> {
let mut out = Vec::new();
let walked = each_truth(ctx, args, |b| out.push(b))?;
Ok(walked.map(|()| out))
}
pub fn number_arg(
ctx: &Ctx<'_>,
args: &[Arg],
at: usize,
) -> Result<Result<f64, ErrorKind>, Reason> {
let Some(arg) = args.get(at) else {
return Ok(Err(ErrorKind::Value));
};
Ok(ctx.one(arg)?.number())
}
fn if_<'e>(ctx: &Ctx<'_>, args: &'e [Expr]) -> Result<Arg<'e>, Reason> {
if args.len() < 2 || args.len() > 3 {
return Ok(Arg::Scalar(Value::Error(ErrorKind::Value)));
}
let condition = match ctx.truth(&args[0])? {
Ok(b) => b,
Err(k) => return Ok(Arg::Scalar(Value::Error(k))),
};
match (condition, args.get(1), args.get(2)) {
(true, Some(Expr::Empty), _) | (false, _, Some(Expr::Empty)) => {
Ok(Arg::Scalar(Value::Number(0.0)))
}
(true, Some(then), _) => ctx.eval(then),
(false, _, Some(otherwise)) => ctx.eval(otherwise),
(false, _, None) => Ok(Arg::Scalar(Value::Bool(false))),
(true, None, _) => Ok(Arg::Scalar(Value::Error(ErrorKind::Value))),
}
}
fn and(ctx: &Ctx<'_>, args: &[Arg]) -> Result<Value, Reason> {
let (mut all, mut any) = (true, false);
let walked = each_truth(ctx, args, |b| {
all &= b;
any = true;
})?;
Ok(match walked {
Err(k) => Value::Error(k),
Ok(()) if !any => Value::Error(ErrorKind::Value),
Ok(()) => Value::Bool(all),
})
}
fn or(ctx: &Ctx<'_>, args: &[Arg]) -> Result<Value, Reason> {
let (mut some, mut any) = (false, false);
let walked = each_truth(ctx, args, |b| {
some |= b;
any = true;
})?;
Ok(match walked {
Err(k) => Value::Error(k),
Ok(()) if !any => Value::Error(ErrorKind::Value),
Ok(()) => Value::Bool(some),
})
}
fn not(ctx: &Ctx<'_>, args: &[Arg]) -> Result<Value, Reason> {
if args.len() != 1 {
return Ok(Value::Error(ErrorKind::Value));
}
Ok(match ctx.truth_arg(&args[0])? {
Ok(b) => Value::Bool(!b),
Err(k) => Value::Error(k),
})
}
fn sum(ctx: &Ctx<'_>, args: &[Arg]) -> Result<Value, Reason> {
let mut total = -0.0;
let walked = each_number(ctx, args, |n| total += n)?;
Ok(match walked {
Err(k) => Value::Error(k),
Ok(()) => finite(total),
})
}
fn min(ctx: &Ctx<'_>, args: &[Arg]) -> Result<Value, Reason> {
let (mut least, mut any) = (f64::INFINITY, false);
let walked = each_number(ctx, args, |n| {
least = least.min(n);
any = true;
})?;
Ok(match walked {
Err(k) => Value::Error(k),
Ok(()) => Value::Number(least.min(if any { f64::INFINITY } else { 0.0 })),
})
}
fn max(ctx: &Ctx<'_>, args: &[Arg]) -> Result<Value, Reason> {
let (mut most, mut any) = (f64::NEG_INFINITY, false);
let walked = each_number(ctx, args, |n| {
most = most.max(n);
any = true;
})?;
Ok(match walked {
Err(k) => Value::Error(k),
Ok(()) => Value::Number(most.max(if any { f64::NEG_INFINITY } else { 0.0 })),
})
}
fn abs(ctx: &Ctx<'_>, args: &[Arg]) -> Result<Value, Reason> {
if args.len() != 1 {
return Ok(Value::Error(ErrorKind::Value));
}
Ok(match number_arg(ctx, args, 0)? {
Ok(n) => Value::Number(n.abs()),
Err(k) => Value::Error(k),
})
}
fn int(ctx: &Ctx<'_>, args: &[Arg]) -> Result<Value, Reason> {
if args.len() != 1 {
return Ok(Value::Error(ErrorKind::Value));
}
Ok(match number_arg(ctx, args, 0)? {
Ok(n) => Value::Number(n.floor()),
Err(k) => Value::Error(k),
})
}
fn modulo(ctx: &Ctx<'_>, args: &[Arg]) -> Result<Value, Reason> {
if args.len() != 2 {
return Ok(Value::Error(ErrorKind::Value));
}
let n = match number_arg(ctx, args, 0)? {
Ok(n) => n,
Err(k) => return Ok(Value::Error(k)),
};
let d = match number_arg(ctx, args, 1)? {
Ok(d) => d,
Err(k) => return Ok(Value::Error(k)),
};
if d == 0.0 {
return Ok(Value::Error(ErrorKind::Div0));
}
Ok(finite(n - d * (n / d).floor()))
}
fn row(ctx: &Ctx<'_>, args: &[Arg]) -> Result<Value, Reason> {
position(ctx, args, |here, area| match area {
Some(a) => a.top,
None => here.1,
})
}
fn column(ctx: &Ctx<'_>, args: &[Arg]) -> Result<Value, Reason> {
position(ctx, args, |here, area| match area {
Some(a) => a.left,
None => here.2,
})
}
fn position(
ctx: &Ctx<'_>,
args: &[Arg],
pick: fn((usize, u32, u32), Option<&super::graph::Area>) -> u32,
) -> Result<Value, Reason> {
let here = ctx.env.here();
match args {
[] => Ok(Value::Number(f64::from(pick(here, None)))),
[Arg::Area { areas, .. }] => match areas.first() {
Some(a) => Ok(Value::Number(f64::from(pick(here, Some(a))))),
None => Ok(Value::Error(ErrorKind::Ref)),
},
_ => Ok(Value::Error(ErrorKind::Value)),
}
}
fn choose<'e>(ctx: &Ctx<'_>, args: &'e [Expr]) -> Result<Arg<'e>, Reason> {
if args.len() < 2 {
return Ok(Arg::Scalar(Value::Error(ErrorKind::Value)));
}
let k = match ctx.scalar(&args[0])?.number() {
Ok(n) => n.floor(),
Err(e) => return Ok(Arg::Scalar(Value::Error(e))),
};
if k < 1.0 || k > (args.len() - 1) as f64 {
return Ok(Arg::Scalar(Value::Error(ErrorKind::Value)));
}
ctx.eval(&args[k as usize])
}
fn sin(ctx: &Ctx<'_>, args: &[Arg]) -> Result<Value, Reason> {
if args.len() != 1 {
return Ok(Value::Error(ErrorKind::Value));
}
Ok(match number_arg(ctx, args, 0)? {
Ok(n) => finite(n.sin()),
Err(k) => Value::Error(k),
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_language_registers_the_day_one_list_exactly() {
let lib = language();
let mut want: Vec<&str> = NAMES.to_vec();
want.sort_unstable();
assert_eq!(lib.names(), want);
assert!(
lib.form("IF").is_some(),
"IF computes only the branch it takes"
);
assert!(
lib.form("CHOOSE").is_some(),
"CHOOSE computes only the value chosen"
);
assert!(lib.strict("SUM").is_some());
}
}