use crate::{
EvaluationBounds, EvaluationError, Evaluator, exp, r#mod,
support::compile_valid
};
const BOUNDARIES: [i32; 9] = [
i32::MIN,
i32::MIN + 1,
-2,
-1,
0,
1,
2,
i32::MAX - 1,
i32::MAX
];
fn brute_force(
lhs: EvaluationBounds,
rhs: EvaluationBounds,
op: impl Fn(i32, i32) -> i32
) -> EvaluationBounds
{
let mut min = i32::MAX;
let mut max = i32::MIN;
for x in lhs.min..=lhs.max
{
for y in rhs.min..=rhs.max
{
let value = op(x, y);
min = min.min(value);
max = max.max(value);
}
}
(min, max).into()
}
fn samples(bounds: EvaluationBounds) -> Vec<i32>
{
let mut values = vec![bounds.min, bounds.max];
values.extend(BOUNDARIES.iter().copied().filter(|&x| bounds.contains(x)));
values
}
fn boundary_intervals() -> Vec<EvaluationBounds>
{
let mut intervals = Vec::new();
for (i, &min) in BOUNDARIES.iter().enumerate()
{
for &max in BOUNDARIES.iter().skip(i)
{
intervals.push((min, max).into());
}
}
intervals
}
const EXP_BASES: std::ops::RangeInclusive<i32> = -20..=20;
const EXP_POWERS: std::ops::RangeInclusive<i32> = -10..=10;
#[test]
fn test_exp_bounds_exhaustive()
{
let mut checked = 0usize;
for base_min in EXP_BASES
{
for base_max in base_min..=*EXP_BASES.end()
{
let base: EvaluationBounds = (base_min, base_max).into();
for power_min in EXP_POWERS
{
for power_max in power_min..=*EXP_POWERS.end()
{
let power: EvaluationBounds = (power_min, power_max).into();
let expected = brute_force(base, power, exp);
let actual = base.exp(power);
assert_eq!(
actual, expected,
"[{}] ^ [{}]: expected [{}], got [{}]",
base, power, expected, actual
);
checked += 1;
}
}
}
}
assert_eq!(checked, 198891);
}
#[test]
fn test_exp_bounds_at_boundaries()
{
for base in boundary_intervals()
{
for power in boundary_intervals()
{
let actual = base.exp(power);
assert!(
actual.min <= actual.max,
"[{}] ^ [{}]: inverted interval [{}]",
base,
power,
actual
);
for x in samples(base)
{
for y in samples(power)
{
let value = exp(x, y);
assert!(
actual.contains(value),
"[{}] ^ [{}]: {} ^ {} = {} ∉ [{}]",
base,
power,
x,
y,
value,
actual
);
}
}
}
}
}
const REM_ENDPOINTS: std::ops::RangeInclusive<i32> = -14..=14;
const WIDE_DIVISORS: [(i32, i32); 12] = [
(-129, 129),
(-200, 60),
(-60, 200),
(0, 140),
(-140, 0),
(1, 140),
(-140, -1),
(14, 153),
(-153, -14),
(200, 340),
(i32::MIN, i32::MIN + 140),
(i32::MAX - 140, i32::MAX)
];
#[test]
fn test_rem_bounds_exhaustive()
{
let mut checked = 0usize;
for min in REM_ENDPOINTS
{
for max in min..=*REM_ENDPOINTS.end()
{
let dividend: EvaluationBounds = (min, max).into();
for divisor_min in REM_ENDPOINTS
{
for divisor_max in divisor_min..=*REM_ENDPOINTS.end()
{
let divisor: EvaluationBounds =
(divisor_min, divisor_max).into();
let expected = brute_force(dividend, divisor, r#mod);
let actual = dividend % divisor;
assert_eq!(
actual, expected,
"[{}] % [{}]: expected [{}], got [{}]",
dividend, divisor, expected, actual
);
checked += 1;
}
}
}
}
assert_eq!(checked, 189225);
}
#[test]
fn test_rem_bounds_wide_divisors()
{
for (divisor_min, divisor_max) in WIDE_DIVISORS
{
let low = match (divisor_min, divisor_max)
{
(min, max) if min <= 0 && max >= 0 => 0,
(min, max) => (min as i64).abs().min((max as i64).abs())
};
let high = (divisor_min as i64).abs().max((divisor_max as i64).abs());
assert!(
high - low >= 128,
"[{}, {}] spans only {} magnitudes",
divisor_min,
divisor_max,
high - low + 1
);
let divisor: EvaluationBounds = (divisor_min, divisor_max).into();
for min in REM_ENDPOINTS
{
for max in min..=*REM_ENDPOINTS.end()
{
let dividend: EvaluationBounds = (min, max).into();
let expected = brute_force(dividend, divisor, r#mod);
let actual = dividend % divisor;
assert!(
actual.min <= actual.max,
"[{}] % [{}]: inverted interval [{}]",
dividend,
divisor,
actual
);
assert!(
actual.min <= expected.min && actual.max >= expected.max,
"[{}] % [{}]: [{}] ⊉ [{}]",
dividend,
divisor,
actual,
expected
);
}
}
}
}
#[test]
fn test_rem_bounds_at_boundaries()
{
for dividend in boundary_intervals()
{
for divisor in boundary_intervals()
{
let actual = dividend % divisor;
assert!(
actual.min <= actual.max,
"[{}] % [{}]: inverted interval [{}]",
dividend,
divisor,
actual
);
for x in samples(dividend)
{
for y in samples(divisor)
{
let value = r#mod(x, y);
assert!(
actual.contains(value),
"[{}] % [{}]: {} % {} = {} ∉ [{}]",
dividend,
divisor,
x,
y,
value,
actual
);
}
}
}
}
}
#[test]
fn test_rem_bounds_regressions()
{
let dividend: EvaluationBounds = (1, 6).into();
let divisor: EvaluationBounds = (-4, -1).into();
assert_eq!(dividend % divisor, (0, 3).into());
let dividend: EvaluationBounds = i32::MIN.into();
let divisor: EvaluationBounds = (0, i32::MAX).into();
let actual = dividend % divisor;
assert!(actual.min <= actual.max, "inverted interval [{}]", actual);
assert!(actual.contains(r#mod(i32::MIN, i32::MAX)));
}
#[test]
fn test_rem_bounds_end_to_end()
{
let function = compile_valid("1D6 % (1D4 - 5)");
let evaluator = Evaluator::new(function);
let bounds = evaluator.bounds_over([], []).unwrap().value;
assert!(bounds.contains(3), "1D6 % (1D4 - 5): 3 ∉ [{}]", bounds);
}
fn union_over_members(
source: &str,
binding: EvaluationBounds
) -> EvaluationBounds
{
let evaluator = Evaluator::new(compile_valid(source));
let mut min = i32::MAX;
let mut max = i32::MIN;
for x in binding.min..=binding.max
{
let bounds = evaluator.bounds_over([Some(x.into())], []).unwrap().value;
min = min.min(bounds.min);
max = max.max(bounds.max);
}
(min, max).into()
}
fn assert_interval_binding(
source: &str,
binding: EvaluationBounds,
expected: EvaluationBounds
)
{
let evaluator = Evaluator::new(compile_valid(source));
let actual = evaluator.bounds_over([Some(binding)], []).unwrap();
assert_eq!(
actual.value, expected,
"{} over [{}]: expected [{}], got [{}]",
source, binding, expected, actual.value
);
assert_eq!(
actual.count, None,
"{} over [{}]: outcome count survived a non-degenerate binding",
source, binding
);
let truth = union_over_members(source, binding);
assert!(
actual.value.min <= truth.min && truth.max <= actual.value.max,
"{} over [{}]: [{}] ⊉ [{}]",
source,
binding,
actual.value,
truth
);
}
#[test]
fn test_bounds_over_interval_argument()
{
assert_interval_binding("x: {x}D6", (1, 20).into(), (1, 120).into());
}
#[test]
fn test_bounds_over_unsupplied_external()
{
let evaluator = Evaluator::new(compile_valid("1D6 + {x}"));
let bounds = evaluator.bounds_over([], []).unwrap();
assert_eq!(bounds.value, (i32::MIN + 1, i32::MAX).into());
assert_eq!(bounds.count, None);
assert!(bounds.value.contains(1) && bounds.value.contains(6));
assert!(bounds.value.contains(i32::MIN + 1));
assert!(bounds.value.contains(i32::MAX));
}
#[test]
fn test_bounds_over_unsupplied_argument()
{
let evaluator = Evaluator::new(compile_valid("x: 1D6 + {x}"));
let bounds = evaluator.bounds_over([None], []).unwrap();
assert_eq!(bounds.value, (i32::MIN + 1, i32::MAX).into());
let bounds = evaluator.bounds_over([Some(10.into())], []).unwrap();
assert_eq!(bounds.value, (11, 16).into());
}
#[test]
fn test_bounds_over_ignores_environment()
{
let mut evaluator = Evaluator::new(compile_valid("1D6 + {x}"));
evaluator.bind("x", 3).unwrap();
let bounds = evaluator.bounds_over([], []).unwrap();
assert_eq!(bounds.value, (i32::MIN + 1, i32::MAX).into());
let bounds = evaluator.bounds_over([], [("x", 3.into())]).unwrap();
assert_eq!(bounds.value, (4, 9).into());
assert_eq!(bounds.count, Some(6));
}
#[test]
fn test_bounds_over_count_requires_degenerate_bindings()
{
let evaluator = Evaluator::new(compile_valid("x: 1D6 + {x}"));
assert_eq!(
evaluator.bounds_over([Some(2.into())], []).unwrap().count,
Some(6)
);
assert_eq!(
evaluator
.bounds_over([Some((2, 3).into())], [])
.unwrap()
.count,
None
);
assert_eq!(evaluator.bounds_over([None], []).unwrap().count, None);
let evaluator = Evaluator::new(compile_valid("1D6 + {x}"));
assert_eq!(evaluator.bounds_over([], []).unwrap().count, None);
assert_eq!(
evaluator.bounds_over([], [("x", 3.into())]).unwrap().count,
Some(6)
);
}
#[test]
fn test_bounds_over_rejects_bad_bindings()
{
let evaluator = Evaluator::new(compile_valid("x: {x}D6"));
assert_eq!(
evaluator.bounds_over([], []),
Err(EvaluationError::BadArity {
expected: 1,
given: 0
})
);
assert_eq!(
evaluator.bounds_over([None, None], []),
Err(EvaluationError::BadArity {
expected: 1,
given: 2
})
);
assert_eq!(
evaluator.bounds_over([None], [("y", 1.into())]),
Err(EvaluationError::UnrecognizedExternal("y"))
);
}
#[test]
fn test_bounds_over_negative_interval_count()
{
assert_interval_binding("x: {x}D6", (-3, 5).into(), (0, 30).into());
let evaluator = Evaluator::new(compile_valid("(1D[-5, -4, 0, 4, 5])D6"));
let bounds = evaluator.bounds_over([], []).unwrap().value;
assert_eq!(bounds, (0, 30).into());
}
#[test]
fn test_bounds_over_interval_faces()
{
assert_interval_binding("x: 1D{x}", (-4, 6).into(), (0, 6).into());
}
#[test]
fn test_bounds_over_interval_drop_count()
{
assert_interval_binding(
"x: 5D6 drop lowest {x}",
(0, 10).into(),
(0, 30).into()
);
assert_interval_binding(
"x: 5D6 drop highest {x}",
(-2, 3).into(),
(2, 30).into()
);
}