use super::eval::{finite, Arg, Ctx, Known, Library, Reason};
use super::formula::Expr;
use super::value::{ErrorKind, Value};
pub fn language() -> Library {
let mut lib = Library::empty();
lib.register_known("IF", if_, Known::If);
lib.register_known_strict("AND", and, Known::And);
lib.register_known_strict("OR", or, Known::Or);
lib.register_known_strict("NOT", not, Known::Not);
lib.register_known_strict("SUM", sum, Known::Sum);
lib.register_known_strict("MIN", min, Known::Min);
lib.register_known_strict("MAX", max, Known::Max);
lib.register_known_strict("ABS", abs, Known::Abs);
lib.register_known_strict("INT", int, Known::Int);
lib.register_known_strict("MOD", modulo, Known::Mod);
lib.register_known_strict("ROW", row, Known::Row);
lib.register_known_strict("COLUMN", column, Known::Column);
lib.register_known("CHOOSE", choose, Known::Choose);
lib.register_known_strict("SIN", sin, Known::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>;
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) enum Ranged<T> {
Counts(T),
Error(ErrorKind),
Skipped,
}
pub(crate) trait Rule {
type Item: Copy;
fn given(v: &Value) -> Result<Self::Item, ErrorKind>;
fn ranged(v: &Value) -> Ranged<Self::Item>;
fn given_number(n: f64) -> Self::Item;
fn given_truth(b: bool) -> Self::Item;
fn ranged_number(n: f64) -> Self::Item;
}
pub(crate) struct OverNumbers;
impl Rule for OverNumbers {
type Item = f64;
fn given(v: &Value) -> Result<f64, ErrorKind> {
v.number()
}
fn ranged(v: &Value) -> Ranged<f64> {
match v {
Value::Number(n) => Ranged::Counts(*n),
Value::Error(k) => Ranged::Error(*k),
_ => Ranged::Skipped,
}
}
#[inline]
fn given_number(n: f64) -> f64 {
n
}
#[inline]
fn given_truth(b: bool) -> f64 {
if b {
1.0
} else {
0.0
}
}
#[inline]
fn ranged_number(n: f64) -> f64 {
n
}
}
pub(crate) struct OverTruths;
impl Rule for OverTruths {
type Item = bool;
fn given(v: &Value) -> Result<bool, ErrorKind> {
v.truth()
}
fn ranged(v: &Value) -> Ranged<bool> {
match v {
Value::Bool(b) => Ranged::Counts(*b),
Value::Number(n) => Ranged::Counts(*n != 0.0),
Value::Error(k) => Ranged::Error(*k),
_ => Ranged::Skipped,
}
}
#[inline]
fn given_number(n: f64) -> bool {
n != 0.0
}
#[inline]
fn given_truth(b: bool) -> bool {
b
}
#[inline]
fn ranged_number(n: f64) -> bool {
n != 0.0
}
}
fn each_item<R: Rule, F: FnMut(R::Item)>(
ctx: &Ctx<'_>,
args: &[Arg],
mut f: F,
) -> Result<Result<(), ErrorKind>, Reason> {
for a in args {
match a {
Arg::Scalar(v) => match R::given(v) {
Ok(x) => f(x),
Err(k) => return Ok(Err(k)),
},
Arg::Area { areas, .. } => {
let mut err: Option<ErrorKind> = None;
ctx.each(areas, |v| {
match R::ranged(&v) {
Ranged::Counts(x) => f(x),
Ranged::Error(k) => {
err.get_or_insert(k);
}
Ranged::Skipped => {}
}
Ok(())
})?;
if let Some(k) = err {
return Ok(Err(k));
}
}
}
}
Ok(Ok(()))
}
pub fn each_number<F: FnMut(f64)>(
ctx: &Ctx<'_>,
args: &[Arg],
f: F,
) -> Result<Result<(), ErrorKind>, Reason> {
each_item::<OverNumbers, F>(ctx, args, f)
}
pub fn each_truth<F: FnMut(bool)>(
ctx: &Ctx<'_>,
args: &[Arg],
f: F,
) -> Result<Result<(), ErrorKind>, Reason> {
each_item::<OverTruths, F>(ctx, args, f)
}
pub(crate) trait Fold: Default {
type Rule: Rule;
fn add(&mut self, x: <Self::Rule as Rule>::Item);
fn answer(self) -> Value;
}
pub(crate) struct Sum(f64);
impl Default for Sum {
fn default() -> Sum {
Sum(-0.0)
}
}
impl Fold for Sum {
type Rule = OverNumbers;
fn add(&mut self, n: f64) {
self.0 += n;
}
fn answer(self) -> Value {
finite(self.0)
}
}
pub(crate) struct Least {
least: f64,
any: bool,
}
impl Default for Least {
fn default() -> Least {
Least {
least: f64::INFINITY,
any: false,
}
}
}
impl Fold for Least {
type Rule = OverNumbers;
fn add(&mut self, n: f64) {
self.least = self.least.min(n);
self.any = true;
}
fn answer(self) -> Value {
Value::Number(self.least.min(if self.any { f64::INFINITY } else { 0.0 }))
}
}
pub(crate) struct Most {
most: f64,
any: bool,
}
impl Default for Most {
fn default() -> Most {
Most {
most: f64::NEG_INFINITY,
any: false,
}
}
}
impl Fold for Most {
type Rule = OverNumbers;
fn add(&mut self, n: f64) {
self.most = self.most.max(n);
self.any = true;
}
fn answer(self) -> Value {
Value::Number(
self.most
.max(if self.any { f64::NEG_INFINITY } else { 0.0 }),
)
}
}
pub(crate) struct All {
all: bool,
any: bool,
}
impl Default for All {
fn default() -> All {
All {
all: true,
any: false,
}
}
}
impl Fold for All {
type Rule = OverTruths;
fn add(&mut self, b: bool) {
self.all &= b;
self.any = true;
}
fn answer(self) -> Value {
if self.any {
Value::Bool(self.all)
} else {
Value::Error(ErrorKind::Value)
}
}
}
#[derive(Default)]
pub(crate) struct Some_ {
some: bool,
any: bool,
}
impl Fold for Some_ {
type Rule = OverTruths;
fn add(&mut self, b: bool) {
self.some |= b;
self.any = true;
}
fn answer(self) -> Value {
if self.any {
Value::Bool(self.some)
} else {
Value::Error(ErrorKind::Value)
}
}
}
fn folded<F: Fold>(ctx: &Ctx<'_>, args: &[Arg]) -> Result<Value, Reason> {
let mut fold = F::default();
Ok(match each_item::<F::Rule, _>(ctx, args, |x| fold.add(x))? {
Err(k) => Value::Error(k),
Ok(()) => fold.answer(),
})
}
pub(crate) fn negated(v: &Value) -> Value {
match v.truth() {
Ok(b) => Value::Bool(!b),
Err(k) => Value::Error(k),
}
}
pub(crate) fn abs_of(n: f64) -> Value {
Value::Number(n.abs())
}
pub(crate) fn int_of(n: f64) -> Value {
Value::Number(n.floor())
}
pub(crate) fn sin_of(n: f64) -> Value {
finite(n.sin())
}
pub(crate) fn mod_of(n: f64, d: f64) -> Value {
if d == 0.0 {
return Value::Error(ErrorKind::Div0);
}
finite(n - d * (n / d).floor())
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Axis {
Row,
Column,
}
impl Axis {
pub(crate) fn of(self, row: u32, column: u32) -> f64 {
f64::from(match self {
Axis::Row => row,
Axis::Column => column,
})
}
}
pub(crate) fn placed(axis: Axis, cell: Option<(u32, u32)>) -> Value {
match cell {
Some((row, column)) => Value::Number(axis.of(row, column)),
None => Value::Error(ErrorKind::Value),
}
}
pub(crate) fn chosen(index: &Value, count: usize) -> Result<usize, ErrorKind> {
let k = index.number()?.floor();
if k < 1.0 || k > count as f64 {
return Err(ErrorKind::Value);
}
Ok(k as usize)
}
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> {
folded::<All>(ctx, args)
}
fn or(ctx: &Ctx<'_>, args: &[Arg]) -> Result<Value, Reason> {
folded::<Some_>(ctx, args)
}
fn not(ctx: &Ctx<'_>, args: &[Arg]) -> Result<Value, Reason> {
if args.len() != 1 {
return Ok(Value::Error(ErrorKind::Value));
}
Ok(negated(&ctx.one(&args[0])?))
}
fn sum(ctx: &Ctx<'_>, args: &[Arg]) -> Result<Value, Reason> {
folded::<Sum>(ctx, args)
}
fn min(ctx: &Ctx<'_>, args: &[Arg]) -> Result<Value, Reason> {
folded::<Least>(ctx, args)
}
fn max(ctx: &Ctx<'_>, args: &[Arg]) -> Result<Value, Reason> {
folded::<Most>(ctx, args)
}
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) => abs_of(n),
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) => int_of(n),
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)),
};
Ok(mod_of(n, d))
}
fn row(ctx: &Ctx<'_>, args: &[Arg]) -> Result<Value, Reason> {
position(ctx, args, Axis::Row)
}
fn column(ctx: &Ctx<'_>, args: &[Arg]) -> Result<Value, Reason> {
position(ctx, args, Axis::Column)
}
fn position(ctx: &Ctx<'_>, args: &[Arg], axis: Axis) -> Result<Value, Reason> {
let (_, row, column) = ctx.env.here();
Ok(match args {
[] => placed(axis, Some((row, column))),
[Arg::Area { areas, .. }] => match areas.first() {
Some(a) => placed(axis, Some((a.top, a.left))),
None => Value::Error(ErrorKind::Ref),
},
_ => placed(axis, None),
})
}
fn choose<'e>(ctx: &Ctx<'_>, args: &'e [Expr]) -> Result<Arg<'e>, Reason> {
if args.len() < 2 {
return Ok(Arg::Scalar(Value::Error(ErrorKind::Value)));
}
match chosen(&ctx.scalar(&args[0])?, args.len() - 1) {
Ok(k) => ctx.eval(&args[k]),
Err(e) => Ok(Arg::Scalar(Value::Error(e))),
}
}
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) => sin_of(n),
Err(k) => Value::Error(k),
})
}
#[cfg(test)]
mod tests {
use super::*;
fn items_agree<R: Rule>(same: impl Fn(R::Item, R::Item) -> bool) {
let numbers = [
0.0,
-0.0,
1.0,
-1.5,
0.25,
1e300,
-1e-300,
f64::MIN_POSITIVE,
];
for n in numbers {
let given = R::given(&Value::Number(n)).expect("a number is an item");
assert!(same(R::given_number(n), given), "given {n}");
let Ranged::Counts(ranged) = R::ranged(&Value::Number(n)) else {
panic!("a number in a range counts")
};
assert!(same(R::ranged_number(n), ranged), "ranged {n}");
}
for b in [true, false] {
let given = R::given(&Value::Bool(b)).expect("a truth value is an item");
assert!(same(R::given_truth(b), given), "given {b}");
}
}
#[test]
fn a_rule_s_items_of_numbers_and_truth_values_are_its_given_and_ranged() {
items_agree::<OverNumbers>(|a, b| a.to_bits() == b.to_bits());
items_agree::<OverTruths>(|a, b| a == b);
}
#[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());
}
}