1use crate::Result;
20use crate::error::Error;
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23enum Expr {
24 Single(i64),
25 Range(Option<i64>, Option<i64>),
26}
27
28fn parse_int(s: &str) -> std::result::Result<i64, String> {
29 s.parse::<i64>()
30 .map_err(|_| format!("invalid integer '{s}'"))
31}
32
33fn parse_expr(tok: &str) -> std::result::Result<Expr, String> {
34 let tok = tok.trim();
35 if tok.is_empty() {
36 return Err("empty index expression".to_string());
37 }
38 if let Some((lhs, rhs)) = tok.split_once(':') {
39 let start = if lhs.is_empty() {
40 None
41 } else {
42 Some(parse_int(lhs)?)
43 };
44 let end = if rhs.is_empty() {
45 None
46 } else {
47 Some(parse_int(rhs)?)
48 };
49 Ok(Expr::Range(start, end))
50 } else {
51 Ok(Expr::Single(parse_int(tok)?))
52 }
53}
54
55fn parse(raw: &str) -> std::result::Result<Vec<Expr>, String> {
56 if raw.trim().is_empty() {
57 return Err("--indices must not be empty".to_string());
58 }
59 raw.split(',').map(parse_expr).collect()
60}
61
62fn resolve_single(i: i64, rowcount: u64) -> Result<u64> {
63 let rc_i: i64 =
64 i64::try_from(rowcount).map_err(|_| Error::IndexOutOfRange { index: i, rowcount })?;
65 let resolved = if i < 0 { rc_i + i } else { i };
66 if resolved < 0 || resolved >= rc_i {
67 return Err(Error::IndexOutOfRange { index: i, rowcount });
68 }
69 Ok(resolved as u64)
70}
71
72pub fn resolve(raw: &str, rowcount: u64) -> Result<Vec<u64>> {
74 let exprs = parse(raw).map_err(Error::IndexParse)?;
75 let rc_i: i64 =
76 i64::try_from(rowcount).map_err(|_| Error::IndexOutOfRange { index: 0, rowcount })?;
77 let mut out = Vec::new();
78 for expr in exprs {
79 match expr {
80 Expr::Single(i) => {
81 if rowcount == 0 {
82 return Err(Error::IndexOutOfRange { index: i, rowcount });
83 }
84 out.push(resolve_single(i, rowcount)?);
85 }
86 Expr::Range(start, end) => {
87 if rowcount == 0 {
88 return Err(Error::IndexOutOfRange {
89 index: start.or(end).unwrap_or(0),
90 rowcount,
91 });
92 }
93 let start_raw = start.unwrap_or(0);
94 let end_raw = end.unwrap_or(rc_i - 1);
95 let s = resolve_single(start_raw, rowcount)?;
96 let e = resolve_single(end_raw, rowcount)?;
97 if s > e {
98 return Err(Error::EmptyRange {
99 start: start_raw,
100 end: end_raw,
101 });
102 }
103 out.extend(s..=e);
104 }
105 }
106 }
107 Ok(out)
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113
114 #[test]
115 fn single_positive() {
116 assert_eq!(resolve("5", 10).unwrap(), vec![5]);
117 }
118
119 #[test]
120 fn single_negative() {
121 assert_eq!(resolve("-1", 10).unwrap(), vec![9]);
122 assert_eq!(resolve("-10", 10).unwrap(), vec![0]);
123 }
124
125 #[test]
126 fn range_closed() {
127 assert_eq!(resolve("2:5", 10).unwrap(), vec![2, 3, 4, 5]);
128 }
129
130 #[test]
131 fn range_open_start() {
132 assert_eq!(resolve(":3", 10).unwrap(), vec![0, 1, 2, 3]);
133 }
134
135 #[test]
136 fn range_open_end() {
137 assert_eq!(resolve("7:", 10).unwrap(), vec![7, 8, 9]);
138 }
139
140 #[test]
141 fn range_full() {
142 assert_eq!(resolve(":", 4).unwrap(), vec![0, 1, 2, 3]);
143 }
144
145 #[test]
146 fn range_negative_to_negative() {
147 assert_eq!(resolve("-5:-1", 10).unwrap(), vec![5, 6, 7, 8, 9]);
149 }
150
151 #[test]
152 fn range_negative_to_positive() {
153 assert_eq!(resolve("-5:9", 10).unwrap(), vec![5, 6, 7, 8, 9]);
154 }
155
156 #[test]
157 fn order_preserved_and_dupes_kept() {
158 assert_eq!(resolve("3,1,1,0:2", 5).unwrap(), vec![3, 1, 1, 0, 1, 2]);
159 }
160
161 #[test]
162 fn out_of_range_positive() {
163 assert!(matches!(
164 resolve("10", 10),
165 Err(Error::IndexOutOfRange { .. })
166 ));
167 }
168
169 #[test]
170 fn out_of_range_negative() {
171 assert!(matches!(
172 resolve("-11", 10),
173 Err(Error::IndexOutOfRange { .. })
174 ));
175 }
176
177 #[test]
178 fn empty_range_error() {
179 assert!(matches!(resolve("5:2", 10), Err(Error::EmptyRange { .. })));
180 }
181
182 #[test]
183 fn invalid_int_error() {
184 assert!(matches!(resolve("abc", 10), Err(Error::IndexParse(_))));
185 }
186
187 #[test]
188 fn empty_input_error() {
189 assert!(matches!(resolve("", 10), Err(Error::IndexParse(_))));
190 }
191
192 #[test]
193 fn empty_dataset_single_error() {
194 assert!(matches!(
195 resolve("0", 0),
196 Err(Error::IndexOutOfRange { .. })
197 ));
198 }
199}