1#![no_std]
15
16extern crate alloc;
17
18use alloc::string::String;
19use alloc::vec::Vec;
20use core::fmt::{self, Write};
21
22const MAX_VALUES: u128 = 10_000_000;
25
26#[derive(Debug, Clone, PartialEq, Eq)]
28#[non_exhaustive]
29pub enum ParseError {
30 EmptyPart,
32 InvalidNumber(String),
34 InvalidRange(String),
36 DescendingRange {
38 start: u64,
40 end: u64,
42 },
43 RangeTooLarge {
46 start: u64,
48 end: u64,
50 },
51}
52
53impl fmt::Display for ParseError {
54 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55 match self {
56 ParseError::EmptyPart => f.write_str("empty range part"),
57 ParseError::InvalidNumber(s) => write!(f, "invalid number: {s:?}"),
58 ParseError::InvalidRange(s) => write!(f, "invalid range: {s:?}"),
59 ParseError::DescendingRange { start, end } => {
60 write!(f, "descending range: {start}-{end}")
61 }
62 ParseError::RangeTooLarge { start, end } => {
63 write!(f, "range too large to expand: {start}-{end}")
64 }
65 }
66 }
67}
68
69impl core::error::Error for ParseError {}
70
71pub fn parse(input: &str) -> Result<Vec<u64>, ParseError> {
81 let trimmed = input.trim();
82 if trimmed.is_empty() {
83 return Ok(Vec::new());
84 }
85
86 let mut out = Vec::new();
87 for raw in trimmed.split(',') {
88 let part = raw.trim();
89 if part.is_empty() {
90 return Err(ParseError::EmptyPart);
91 }
92 let mut bounds = part.split('-');
93 let first = bounds.next().unwrap_or("").trim();
94 match bounds.next() {
95 None => out.push(number(first)?),
96 Some(second) => {
97 if bounds.next().is_some() {
98 return Err(ParseError::InvalidRange(part.into()));
99 }
100 let start = number(first)?;
101 let end = number(second.trim())?;
102 if start > end {
103 return Err(ParseError::DescendingRange { start, end });
104 }
105 let span = u128::from(end - start) + 1;
107 if out.len() as u128 + span > MAX_VALUES {
108 return Err(ParseError::RangeTooLarge { start, end });
109 }
110 out.extend(start..=end);
111 }
112 }
113 }
114 Ok(out)
115}
116
117fn number(s: &str) -> Result<u64, ParseError> {
118 if s.starts_with('+') {
121 return Err(ParseError::InvalidNumber(s.into()));
122 }
123 s.parse().map_err(|_| ParseError::InvalidNumber(s.into()))
124}
125
126#[must_use]
129pub fn format(values: &[u64]) -> String {
130 if values.is_empty() {
131 return String::new();
132 }
133 let mut sorted = values.to_vec();
134 sorted.sort_unstable();
135 sorted.dedup();
136
137 let mut out = String::new();
138 let mut i = 0;
139 while i < sorted.len() {
140 let start = sorted[i];
141 let mut end = start;
142 while i + 1 < sorted.len() && end.checked_add(1) == Some(sorted[i + 1]) {
144 end = sorted[i + 1];
145 i += 1;
146 }
147 if !out.is_empty() {
148 out.push(',');
149 }
150 if start == end {
152 let _ = write!(out, "{start}");
153 } else {
154 let _ = write!(out, "{start}-{end}");
155 }
156 i += 1;
157 }
158 out
159}