use crate::bounds::side::Side;
use anyhow::{Result, bail};
use std::cmp::Ordering;
use std::fmt;
use std::ops::Range;
use std::str::FromStr;
#[derive(Debug, Eq, Clone)]
pub struct UserBounds {
l: Side,
r: Side,
is_last: bool,
fallback_oob: Option<Vec<u8>>,
}
impl UserBounds {
pub fn new(l: Side, r: Side) -> Self {
Self {
l,
r,
is_last: false,
fallback_oob: None,
}
}
#[inline(always)]
pub fn l(&self) -> &Side {
&self.l
}
#[inline(always)]
pub fn r(&self) -> &Side {
&self.r
}
#[inline(always)]
pub fn is_last(&self) -> bool {
self.is_last
}
#[inline(always)]
pub fn set_is_last(&mut self, is_last: bool) {
self.is_last = is_last;
}
#[inline(always)]
pub fn fallback_oob(&self) -> &Option<Vec<u8>> {
&self.fallback_oob
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum BoundOrFiller {
Bound(UserBounds),
Filler(Vec<u8>),
}
impl fmt::Display for UserBounds {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match (self.l, self.r) {
(l, r) if l == r => write!(f, "{}", l),
(l, r) if r.value_unchecked() == Side::max_right() => write!(f, "{}:-1", l),
(l, r) => write!(f, "{}:{}", l, r),
}
}
}
impl FromStr for UserBounds {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.is_empty() {
bail!("Field format error: empty field");
} else if s == ":" {
bail!("Field format error, no numbers next to `:`");
}
let mut fallback_oob: Option<Vec<u8>> = None;
let mut s = s;
if let Some((range_part, fallback)) = s.split_once('=') {
fallback_oob = Some(fallback.into());
s = range_part;
}
let (l, r) = match s.find(':') {
None => {
let side = Side::from_str_left_bound(s)?;
(side, side)
}
Some(idx_colon) if idx_colon == 0 => (
Side::with_pos_value(0),
Side::from_str_right_bound(&s[idx_colon + 1..])?,
),
Some(idx_colon) if idx_colon == s.len() - 1 => (
Side::from_str_left_bound(&s[..idx_colon])?,
Side::with_pos_inf(),
),
Some(idx_colon) => (
Side::from_str_left_bound(&s[..idx_colon])?,
Side::from_str_right_bound(&s[idx_colon + 1..])?,
),
};
if l != r {
if !l.is_negative() && !r.is_negative() && r.value_unchecked() < l.value_unchecked() {
bail!("Field left value cannot be greater than right value");
} else if l.is_negative()
&& r.is_negative()
&& l.value_unchecked() < r.value_unchecked()
{
bail!("Field left value cannot be greater than right value")
}
}
let mut b = UserBounds::new(l, r);
b.fallback_oob = fallback_oob;
Ok(b)
}
}
impl PartialOrd for UserBounds {
#[inline(always)]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.r.partial_cmp(&other.l)
}
}
impl PartialEq for UserBounds {
#[inline(always)]
fn eq(&self, other: &Self) -> bool {
(self.l, self.r) == (other.l, other.r)
}
}
impl Default for UserBounds {
fn default() -> Self {
UserBounds::new(Side::with_pos_value(0), Side::with_pos_inf())
}
}
pub trait UserBoundsTrait {
fn new(l: Side, r: Side) -> Self;
fn with_fallback(l: Side, r: Side, fallback_oob: Option<Vec<u8>>) -> Self;
fn try_into_range(&self, parts_length: usize) -> Result<Range<usize>>;
fn matches(&self, idx: usize) -> Result<bool>;
fn unpack(&self, num_fields: usize) -> Vec<UserBounds>;
fn complement(&self, num_fields: usize) -> Result<Vec<UserBounds>>;
}
impl UserBoundsTrait for UserBounds {
fn new(l: Side, r: Side) -> Self {
UserBounds {
l,
r,
is_last: false,
fallback_oob: None,
}
}
fn with_fallback(l: Side, r: Side, fallback_oob: Option<Vec<u8>>) -> Self {
UserBounds {
l,
r,
is_last: false,
fallback_oob,
}
}
#[inline(always)]
fn matches(&self, idx: usize) -> Result<bool> {
let (l_is_negative, l_value) = self.l.value();
let (r_is_negative, r_value) = self.r.value();
if l_is_negative ^ r_is_negative {
bail!(
"sign mismatch. Can't verify if index {} is between bounds {}",
idx + 1,
self
)
}
Ok((l_value..=r_value).contains(&(idx)))
}
fn try_into_range(&self, parts_length: usize) -> Result<Range<usize>> {
let r_value = std::cmp::min(self.r.value_unchecked(), parts_length - 1);
if self.l.value_unchecked() >= parts_length {
bail!("Out of bounds: {}", self.l);
} else if r_value >= parts_length {
bail!("Out of bounds: {}", self.r);
};
let start = if self.l.is_negative() {
parts_length - self.l.value_unchecked() - 1
} else {
self.l.value_unchecked()
};
let end = if self.r.is_negative() {
parts_length - r_value
} else {
r_value + 1
};
if end <= start {
bail!("Field left value cannot be greater than right value");
}
Ok(Range { start, end })
}
fn unpack(&self, num_fields: usize) -> Vec<UserBounds> {
let mut bounds = Vec::new();
const RIGHT_MAX: usize = Side::max_right();
let (start, end): (usize, usize) = match (self.l.value(), self.r.value()) {
((l_is_negative, l_value), (_, RIGHT_MAX)) => (
if l_is_negative {
num_fields - l_value - 1
} else {
l_value
},
num_fields - 1,
),
((l_is_negative, l_value), (r_is_negative, r_value)) => (
if l_is_negative {
num_fields - l_value - 1
} else {
l_value
},
if r_is_negative {
num_fields - r_value - 1
} else {
r_value
},
),
};
for i in start..=end {
bounds.push(UserBounds::new(
Side::with_pos_value(i),
Side::with_pos_value(i),
))
}
bounds
}
fn complement(&self, num_fields: usize) -> Result<Vec<UserBounds>> {
let r = self.try_into_range(num_fields)?;
let r_complement = complement_std_range(num_fields, &r);
Ok(r_complement
.into_iter()
.map(|x| {
UserBounds::new(
Side::with_pos_value(x.start),
Side::with_pos_value(x.end - 1),
)
})
.collect())
}
}
fn complement_std_range(parts_length: usize, r: &Range<usize>) -> Vec<Range<usize>> {
match (r.start, r.end) {
(0, end) if end == parts_length => Vec::new(),
#[allow(clippy::single_range_in_vec_init)]
(0, right) => vec![right..parts_length],
#[allow(clippy::single_range_in_vec_init)]
(left, end) if end == parts_length => vec![0..left],
(left, right) => vec![0..left, right..parts_length],
}
}
#[cfg(test)]
mod tests {
use super::*;
fn side_pos(l: usize) -> Side {
Side::with_pos_value(l)
}
fn side_neg(l: usize) -> Side {
Side::with_neg_value(l)
}
#[test]
fn test_complement_std_range() {
let empty_vec: Vec<Range<usize>> = vec![];
assert_eq!(complement_std_range(1, &(0..1)), empty_vec);
assert_eq!(complement_std_range(5, &(0..5)), empty_vec);
assert_eq!(complement_std_range(5, &(0..3)), vec![3..5]);
assert_eq!(complement_std_range(5, &(3..5)), vec![0..3]);
assert_eq!(complement_std_range(5, &(1..3)), vec![0..1, 3..5]);
assert_eq!(complement_std_range(2, &(0..2)), empty_vec);
assert_eq!(complement_std_range(2, &(0..1)), vec![1..2]);
assert_eq!(complement_std_range(2, &(1..2)), vec![0..1]);
}
#[test]
fn test_user_bounds_formatting() {
assert_eq!(UserBounds::from_str("1:").unwrap().to_string(), "1:-1");
assert_eq!(UserBounds::from_str(":3").unwrap().to_string(), "1:3");
assert_eq!(UserBounds::from_str("3:").unwrap().to_string(), "3:-1");
assert_eq!(UserBounds::from_str("1:2").unwrap().to_string(), "1:2");
assert_eq!(UserBounds::from_str("-2:-1").unwrap().to_string(), "-2:-1");
}
#[test]
fn test_user_bounds_from_str() {
assert_eq!(
UserBounds::from_str("1").ok(),
Some(UserBounds::new(side_pos(0), side_pos(0)))
);
assert_eq!(
UserBounds::from_str("-1").ok(),
Some(UserBounds::new(side_neg(0), side_neg(0)))
);
assert_eq!(
UserBounds::from_str("1:2").ok(),
Some(UserBounds::new(side_pos(0), side_pos(1)))
);
assert_eq!(
UserBounds::from_str("-2:-1").ok(),
Some(UserBounds::new(side_neg(1), side_neg(0)))
);
assert_eq!(
UserBounds::from_str("1:").ok(),
Some(UserBounds::new(side_pos(0), Side::with_pos_inf())),
);
assert_eq!(
UserBounds::from_str("-1:").ok(),
Some(UserBounds::new(side_neg(0), Side::with_pos_inf())),
);
assert_eq!(
UserBounds::from_str(":1").ok(),
Some(UserBounds::new(Side::with_pos_value(0), side_pos(0))),
);
assert_eq!(
UserBounds::from_str(":-1").ok(),
Some(UserBounds::new(Side::with_pos_value(0), side_neg(0))),
);
assert_eq!(
UserBounds::from_str("1").ok(),
Some(UserBounds::with_fallback(side_pos(0), side_pos(0), None)),
);
assert_eq!(
UserBounds::from_str("1=foo").ok(),
Some(UserBounds::with_fallback(
side_pos(0),
side_pos(0),
Some("foo".as_bytes().to_owned())
)),
);
assert_eq!(
UserBounds::from_str("1:2=foo").ok(),
Some(UserBounds::with_fallback(
side_pos(0),
side_pos(1),
Some("foo".as_bytes().to_owned())
)),
);
assert_eq!(
UserBounds::from_str("-1=foo").ok(),
Some(UserBounds::with_fallback(
side_neg(0),
side_neg(0),
Some("foo".as_bytes().to_owned())
)),
);
assert_eq!(
UserBounds::from_str("1=allow:colon:in:fallback").ok(),
Some(UserBounds::with_fallback(
side_pos(0),
side_pos(0),
Some("allow:colon:in:fallback".as_bytes().to_owned())
)),
);
assert_eq!(
UserBounds::from_str("1:2=allow:colon:in:fallback").ok(),
Some(UserBounds::with_fallback(
side_pos(0),
side_pos(1),
Some("allow:colon:in:fallback".as_bytes().to_owned())
)),
);
{
#![allow(clippy::bind_instead_of_map)]
assert_eq!(
UserBounds::from_str("2:1")
.err()
.and_then(|x| Some(x.to_string())),
Some(String::from(
"Field left value cannot be greater than right value"
))
);
assert_eq!(
UserBounds::from_str("-1:-2")
.err()
.and_then(|x| Some(x.to_string())),
Some(String::from(
"Field left value cannot be greater than right value"
))
);
}
}
#[test]
fn test_unpack_bound() {
assert_eq!(
UserBounds::from_str("1").unwrap().unpack(2),
vec![UserBounds::from_str("1").unwrap()],
);
assert_eq!(
UserBounds::from_str("1:").unwrap().unpack(2),
vec![
UserBounds::from_str("1").unwrap(),
UserBounds::from_str("2").unwrap()
],
);
assert_eq!(
UserBounds::from_str(":2").unwrap().unpack(2),
vec![
UserBounds::from_str("1").unwrap(),
UserBounds::from_str("2").unwrap()
],
);
assert_eq!(
UserBounds::from_str("1:-1").unwrap().unpack(2),
vec![
UserBounds::from_str("1").unwrap(),
UserBounds::from_str("2").unwrap()
],
);
assert_eq!(
UserBounds::from_str("-1:").unwrap().unpack(2),
vec![UserBounds::from_str("2").unwrap()],
);
assert_eq!(
UserBounds::from_str(":-1").unwrap().unpack(2),
vec![
UserBounds::from_str("1").unwrap(),
UserBounds::from_str("2").unwrap()
],
);
assert_eq!(
UserBounds::from_str("-2:-1").unwrap().unpack(2),
vec![
UserBounds::from_str("1").unwrap(),
UserBounds::from_str("2").unwrap()
],
);
}
#[test]
fn test_complement_bound() {
assert_eq!(
UserBounds::from_str("1:1").unwrap().complement(2).unwrap(),
vec![UserBounds::from_str("2:2").unwrap()],
);
assert_eq!(
UserBounds::from_str("1:").unwrap().complement(2).unwrap(),
Vec::new(),
);
assert_eq!(
UserBounds::from_str("-3:3").unwrap().complement(4).unwrap(),
vec![
UserBounds::from_str("1:1").unwrap(),
UserBounds::from_str("4:4").unwrap(),
],
);
}
}