use crate::errors::SelectError;
#[derive(Debug, Clone, Copy, PartialEq)]
enum NumberToken {
Number(usize),
Range,
Lower,
LowerOrEqual,
Greater,
GreaterOrEqual,
}
impl NumberToken {
fn str2number(string: &str) -> Result<Self, SelectError> {
match string.parse::<usize>() {
Ok(number) => Ok(NumberToken::Number(number)),
Err(_) => Err(SelectError::InvalidNumber("".to_string())),
}
}
fn extract_number(self) -> Result<usize, SelectError> {
match self {
NumberToken::Number(n) => Ok(n),
_ => Err(SelectError::InvalidNumber("".to_string())),
}
}
}
fn tokenize_numbers(token: &[String]) -> Result<Vec<NumberToken>, SelectError> {
let mut tokens: Vec<NumberToken> = Vec::new();
let mut current = String::new();
let joined = token.join(" ");
for char in joined.chars() {
if char == '-' {
if !current.is_empty() {
tokens.push(NumberToken::str2number(¤t)?);
current.clear();
}
tokens.push(NumberToken::Range);
}
else if char == '<' || char == '>' {
if !current.is_empty() {
tokens.push(NumberToken::str2number(¤t)?);
current.clear();
}
current.push(char);
}
else if char == '=' {
if current.is_empty() {
return Err(SelectError::InvalidNumber("".to_string()));
}
current.push(char);
match current.as_str() {
">=" => tokens.push(NumberToken::GreaterOrEqual),
"<=" => tokens.push(NumberToken::LowerOrEqual),
_ => return Err(SelectError::InvalidNumber("".to_string())),
}
current.clear();
}
else if char.is_whitespace() {
if !current.is_empty() {
match current.as_str() {
">" => tokens.push(NumberToken::Greater),
"<" => tokens.push(NumberToken::Lower),
anything => tokens.push(NumberToken::str2number(anything)?),
}
current.clear();
}
}
else if char.is_ascii_digit() {
match current.as_str() {
">" => {
tokens.push(NumberToken::Greater);
current.clear();
}
"<" => {
tokens.push(NumberToken::Lower);
current.clear();
}
_ => (),
}
current.push(char);
} else {
return Err(SelectError::InvalidNumber("".to_string()));
}
}
if !current.is_empty() {
tokens.push(NumberToken::str2number(¤t)?);
}
Ok(tokens)
}
pub fn parse_numbers(token: &[String]) -> Result<Vec<(usize, usize)>, SelectError> {
let tokens = tokenize_numbers(token)?;
let mut numbers: Vec<(usize, usize)> = Vec::new();
let mut t = 0;
while t < tokens.len() {
let token = tokens[t];
match token {
NumberToken::Number(n) => {
if t + 1 < tokens.len() && tokens[t + 1] == NumberToken::Range {
t += 1;
continue;
}
numbers.push((n, n));
t += 1;
}
NumberToken::Range => {
if t == 0 || t + 1 == tokens.len() {
return Err(SelectError::InvalidNumber("".to_string()));
}
let previous = tokens[t - 1].extract_number()?;
let next = tokens[t + 1].extract_number()?;
if previous > next {
return Err(SelectError::InvalidNumber("".to_string()));
}
numbers.push((previous, next));
t += 2;
}
_ => {
if t + 1 == tokens.len() {
return Err(SelectError::InvalidNumber("".to_string()));
}
let next = tokens[t + 1].extract_number()?;
match token {
NumberToken::Greater => numbers.push((next + 1, usize::MAX)),
NumberToken::GreaterOrEqual => numbers.push((next, usize::MAX)),
NumberToken::Lower if next > 1 => numbers.push((1, next - 1)),
NumberToken::Lower if next <= 1 => (),
NumberToken::LowerOrEqual => numbers.push((1, next)),
_ => panic!("FATAL GROAN ERROR | numbers::parse_numbers | Impossible match condition reached."),
}
t += 2;
}
}
}
Ok(numbers)
}