#![no_std]
extern crate alloc;
use alloc::string::String;
use alloc::vec::Vec;
use core::fmt::{self, Write};
const MAX_VALUES: u128 = 10_000_000;
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum ParseError {
EmptyPart,
InvalidNumber(String),
InvalidRange(String),
DescendingRange {
start: u64,
end: u64,
},
RangeTooLarge {
start: u64,
end: u64,
},
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ParseError::EmptyPart => f.write_str("empty range part"),
ParseError::InvalidNumber(s) => write!(f, "invalid number: {s:?}"),
ParseError::InvalidRange(s) => write!(f, "invalid range: {s:?}"),
ParseError::DescendingRange { start, end } => {
write!(f, "descending range: {start}-{end}")
}
ParseError::RangeTooLarge { start, end } => {
write!(f, "range too large to expand: {start}-{end}")
}
}
}
}
impl core::error::Error for ParseError {}
pub fn parse(input: &str) -> Result<Vec<u64>, ParseError> {
let trimmed = input.trim();
if trimmed.is_empty() {
return Ok(Vec::new());
}
let mut out = Vec::new();
for raw in trimmed.split(',') {
let part = raw.trim();
if part.is_empty() {
return Err(ParseError::EmptyPart);
}
let mut bounds = part.split('-');
let first = bounds.next().unwrap_or("").trim();
match bounds.next() {
None => out.push(number(first)?),
Some(second) => {
if bounds.next().is_some() {
return Err(ParseError::InvalidRange(part.into()));
}
let start = number(first)?;
let end = number(second.trim())?;
if start > end {
return Err(ParseError::DescendingRange { start, end });
}
let span = u128::from(end - start) + 1;
if out.len() as u128 + span > MAX_VALUES {
return Err(ParseError::RangeTooLarge { start, end });
}
out.extend(start..=end);
}
}
}
Ok(out)
}
fn number(s: &str) -> Result<u64, ParseError> {
if s.starts_with('+') {
return Err(ParseError::InvalidNumber(s.into()));
}
s.parse().map_err(|_| ParseError::InvalidNumber(s.into()))
}
#[must_use]
pub fn format(values: &[u64]) -> String {
if values.is_empty() {
return String::new();
}
let mut sorted = values.to_vec();
sorted.sort_unstable();
sorted.dedup();
let mut out = String::new();
let mut i = 0;
while i < sorted.len() {
let start = sorted[i];
let mut end = start;
while i + 1 < sorted.len() && end.checked_add(1) == Some(sorted[i + 1]) {
end = sorted[i + 1];
i += 1;
}
if !out.is_empty() {
out.push(',');
}
if start == end {
let _ = write!(out, "{start}");
} else {
let _ = write!(out, "{start}-{end}");
}
i += 1;
}
out
}