use crate::{ast::RangeCase, INT};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
use crate::grain::bytecode::SwitchRange;
fn bounds(range: &RangeCase) -> (INT, INT, bool) {
match range {
RangeCase::ExclusiveInt(r, ..) => (r.start, r.end, false),
RangeCase::InclusiveInt(r, ..) => (*r.start(), *r.end(), true),
}
}
enum Atom {
Point(INT),
Between(INT, INT),
}
fn covers(range: &RangeCase, atom: &Atom) -> bool {
match atom {
Atom::Point(point) => range.contains_int(*point),
Atom::Between(low, high) => {
let (start, end, ..) = bounds(range);
start <= *low && end >= *high
}
}
}
struct Run {
from: INT,
to: INT,
inclusive: bool,
blocks: Vec<usize>,
}
pub(crate) fn split(ranges: &[RangeCase]) -> Vec<(SwitchRange, Vec<usize>)> {
let mut points: Vec<INT> = Vec::with_capacity(ranges.len() * 2);
for range in ranges {
let (start, end, ..) = bounds(range);
points.push(start);
points.push(end);
}
points.sort_unstable();
points.dedup();
let mut atoms = Vec::with_capacity(points.len() * 2);
for (index, point) in points.iter().enumerate() {
atoms.push(Atom::Point(*point));
if let Some(next) = points.get(index + 1) {
atoms.push(Atom::Between(*point, *next));
}
}
let mut out: Vec<(SwitchRange, Vec<usize>)> = Vec::new();
let mut run: Option<Run> = None;
let finish = |run: Run| {
(
SwitchRange {
from: run.from,
to: run.to,
inclusive: run.inclusive,
target: 0,
},
run.blocks,
)
};
for atom in &atoms {
let blocks: Vec<usize> = ranges
.iter()
.filter(|range| covers(range, atom))
.map(RangeCase::index)
.collect();
let (from, to, inclusive) = match atom {
Atom::Point(point) => (*point, *point, true),
Atom::Between(low, high) => (*low, *high, false),
};
if run.as_ref().map_or(false, |open| open.blocks == blocks) {
let open = run.as_mut().expect("just checked");
open.to = to;
open.inclusive = inclusive;
continue;
}
if let Some(open) = run.take() {
out.push(finish(open));
}
if !blocks.is_empty() {
run = Some(Run {
from,
to,
inclusive,
blocks,
});
}
}
if let Some(open) = run.take() {
out.push(finish(open));
}
out
}
#[cfg(test)]
mod tests {
use super::*;
fn exclusive(from: INT, to: INT, block: usize) -> RangeCase {
let mut case: RangeCase = (from..to).into();
case.set_index(block);
case
}
fn inclusive(from: INT, to: INT, block: usize) -> RangeCase {
let mut case: RangeCase = (from..=to).into();
case.set_index(block);
case
}
fn table(ranges: &[RangeCase]) -> Vec<(INT, INT, bool, Vec<usize>)> {
split(ranges)
.into_iter()
.map(|(range, blocks)| (range.from, range.to, range.inclusive, blocks))
.collect()
}
#[test]
fn nothing_in_gives_nothing_out() {
assert!(split(&[]).is_empty());
}
#[test]
fn arms_that_do_not_overlap_come_back_unchanged() {
assert_eq!(
table(&[inclusive(0, 9, 0), inclusive(10, 99, 1)]),
vec![(0, 9, true, vec![0]), (10, 99, true, vec![1])],
);
}
#[test]
fn identical_arms_become_one_entry_running_both() {
assert_eq!(
table(&[inclusive(0, 9, 0), inclusive(0, 9, 1)]),
vec![(0, 9, true, vec![0, 1])],
);
}
#[test]
fn a_partial_overlap_splits_into_three() {
assert_eq!(
table(&[exclusive(0, 10, 0), exclusive(5, 20, 1)]),
vec![
(0, 5, false, vec![0]),
(5, 10, false, vec![0, 1]),
(10, 20, false, vec![1]),
],
);
}
#[test]
fn an_inclusive_end_meeting_an_exclusive_start_splits_at_the_point() {
let split = table(&[inclusive(0, 10, 0), exclusive(10, 20, 1)]);
assert_eq!(
split,
vec![
(0, 10, false, vec![0]),
(10, 10, true, vec![0, 1]),
(10, 20, false, vec![1]),
],
);
let (shared, ..) = &split[1];
assert!(*shared == 10, "the point entry must come first");
}
#[test]
fn a_gap_between_arms_is_left_out() {
assert_eq!(
table(&[inclusive(0, 4, 0), inclusive(10, 14, 1)]),
vec![(0, 4, true, vec![0]), (10, 14, true, vec![1])],
);
}
#[test]
fn a_nested_arm_splits_the_one_around_it() {
assert_eq!(
table(&[exclusive(0, 100, 0), exclusive(10, 20, 1)]),
vec![
(0, 10, false, vec![0]),
(10, 20, false, vec![0, 1]),
(20, 100, false, vec![0]),
],
);
}
#[test]
fn every_value_reaches_the_arms_rhai_would_have_run() {
let arms = [
exclusive(0, 10, 0),
inclusive(5, 20, 1),
exclusive(20, 25, 2),
inclusive(-5, 0, 3),
];
let split = split(&arms);
let mut probes: Vec<rhai::Dynamic> = Vec::new();
for value in -8..=28 {
probes.push(rhai::Dynamic::from(value as INT));
#[cfg(not(feature = "no_float"))]
probes.push(rhai::Dynamic::from(value as rhai::FLOAT + 0.5));
}
for probe in &probes {
let expected: Vec<usize> = arms
.iter()
.filter(|arm| arm.contains(probe))
.map(RangeCase::index)
.collect();
let found = split
.iter()
.find(|(range, ..)| range.contains(probe))
.map(|(.., blocks)| blocks.clone())
.unwrap_or_default();
assert_eq!(found, expected, "for {probe:?}");
}
}
}