#[cfg(test)]
mod test;
use ::num_traits::{Bounded, Num, NumAssign};
use ::thiserror::Error;
pub fn parse<T>(value: &str) -> Result<RangeIterator<T>, ParseError<<T as Num>::FromStrRadixErr>>
where
T: NumAssign + Clone + Bounded + PartialOrd,
{
if value.is_empty() {
return Ok(RangeIterator { ranges: Vec::new() });
}
let mut ranges = Vec::new();
let mut token1 = String::new();
let mut token2 = String::new();
let mut first_token = true;
for (n, c) in format!("{value},").chars().enumerate() {
match c {
',' => {
match (token1.is_empty(), token2.is_empty(), first_token) {
(true, true, true) => return Err(ParseError::EmptyRange(n)),
(true, true, false) => return Err(ParseError::InvalidRange(n)),
(false, false, _) => {
let num1 = parse_num(n, &token1)?;
let num2 = parse_num(n, &token2)?;
ranges.push([num1, num2]);
}
(false, true, true) => {
let num: T = parse_num(n, &token1)?;
ranges.push([num.clone(), num]);
}
(false, true, false) => {
let num = parse_num(n, &token1)?;
ranges.push([num, T::max_value()]);
}
(true, false, _) => {
let num = parse_num(n, &token2)?;
ranges.push([T::one(), num]);
}
}
token1.clear();
token2.clear();
first_token = true;
}
'-' => {
if first_token {
first_token = false
} else {
return Err(ParseError::InvalidRange(n));
}
}
'0'..='9' => {
if first_token {
token1.push(c)
} else {
token2.push(c)
}
}
c => return Err(ParseError::IllegalChar(n, c)),
}
}
ranges.reverse();
Ok(RangeIterator { ranges })
}
fn parse_num<T>(n: usize, num: &str) -> Result<T, ParseError<<T as Num>::FromStrRadixErr>>
where
T: Num,
{
match T::from_str_radix(num, 10) {
Ok(number) => Ok(number),
Err(error) => Err(ParseError::ParseNumber(n, num.to_string(), error)),
}
}
pub struct RangeIterator<T> {
ranges: Vec<[T; 2]>,
}
impl<T> Iterator for RangeIterator<T>
where
T: NumAssign + Clone + PartialOrd,
{
type Item = T;
fn next(&mut self) -> Option<T> {
match self.ranges.last_mut() {
Some([ref mut start, end]) => {
if start < end {
let result = start.clone();
*start += T::one();
Some(result)
} else if start == end {
let [start, _] = self.ranges.pop().unwrap();
Some(start)
} else {
self.ranges.pop();
self.next()
}
}
None => None,
}
}
}
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum ParseError<T> {
#[error("Input contains an invalid character `{1}` at position {0}")]
IllegalChar(usize, char),
#[error("Input contains an empty range at position {0}")]
EmptyRange(usize),
#[error("Input contains an invalid range at position {0}")]
InvalidRange(usize),
#[error("Input contains an invalid number `{1}` at position {0}")]
ParseNumber(usize, String, #[source] T),
}