ctoolbox/formats/eite/util/
array.rs

1pub fn arr_empty<T>(a: &[T]) -> bool {
2    a.is_empty()
3}
4pub fn arr_nonempty<T>(a: &[T]) -> bool {
5    !a.is_empty()
6}
7
8pub fn arr_eq<T: PartialEq>(a: &[T], b: &[T]) -> bool {
9    if a.len() != b.len() {
10        return false;
11    }
12    a.iter().zip(b).all(|(x, y)| x == y)
13}
14
15/// count(array)
16pub fn count<T>(a: &[T]) -> usize {
17    a.len()
18}
19
20// ================
21// ARRAY UTILITIES
22// ================
23
24pub fn append_vec<T: Clone>(a: &[T], b: &[T]) -> Vec<T> {
25    let mut out = Vec::with_capacity(a.len() + b.len());
26    out.extend_from_slice(a);
27    out.extend_from_slice(b);
28    out
29}
30
31pub fn js_compat_array_slice<T: Clone>(
32    a: &[T],
33    start: i64,
34    end_exclusive: i64,
35) -> Option<Vec<T>> {
36    let len = i64::try_from(a.len()).expect("usize not fit in i64");
37
38    // Convert negative indices relative to length
39    let mut s = if start < 0 { len + start } else { start };
40    let mut e = if end_exclusive < 0 {
41        len + end_exclusive
42    } else {
43        end_exclusive
44    };
45
46    // Clamp to [0, len]
47    if s < 0 {
48        s = 0;
49    }
50    if e < 0 {
51        e = 0;
52    }
53    if s > len {
54        s = len;
55    }
56    if e > len {
57        e = len;
58    }
59
60    // If end <= start return empty vec (JS slice returns empty array)
61    if e <= s {
62        return None;
63    }
64
65    let s = usize::try_from(s).ok()?;
66    let e = usize::try_from(e).ok()?;
67    let slice = a.get(s..e)?;
68
69    Some(slice.to_vec())
70}
71
72/// Close translation of StageL anSubset.
73pub fn subset<T: Clone>(
74    a: &[T],
75    mut start: i64,
76    mut end: i64,
77) -> Option<Vec<T>> {
78    let mut count = i64::try_from(a.len()).expect("usize not fit in i64");
79
80    if start < 0 {
81        start += count;
82    }
83    if end < 0 {
84        end += count;
85    }
86
87    /* Copilot suggests:
88       if start < 0 || end > len || start > end {
89           return None;
90       }
91
92       let mut res = Vec::new();
93       let mut i = start;
94
95       while i < end {
96           // Safe to call `a.get(i as usize)` because of the bounds check above.
97           if let Some(item) = a.get(i as usize) {
98               res.push(item.clone());
99           } else {
100               // This should not happen because of the bounds check, but just in case
101               return None;
102           }
103           i += 1;
104       }
105
106       Some(res)
107    */
108
109    count = end;
110    let mut res: Vec<T> = Vec::new();
111
112    let mut i = i128::from(start);
113    let count_i128 = i128::from(count);
114    while i <= count_i128 {
115        if let Some(item) = a.get(usize::try_from(i).ok()?) {
116            res.push(item.clone());
117        } else {
118            return None;
119        }
120        i += 1;
121    }
122
123    Some(res)
124}
125
126/// JS pop(array) -> subset(array, 0, -2) meaning drop last element.
127/// We replicate safe behavior (no negative wrap).
128pub fn pop<T: Clone>(a: &[T]) -> Vec<T> {
129    if a.is_empty() {
130        Vec::new()
131    } else {
132        a[..a.len() - 1].to_vec()
133    }
134}
135
136/// shift(array) -> subset(array, 1, -1) => drop first element
137pub fn shift<T: Clone>(a: &[T]) -> Vec<T> {
138    if a.len() <= 1 {
139        Vec::new()
140    } else {
141        a[1..].to_vec()
142    }
143}
144
145/// first(array)
146pub fn first<T: Clone>(a: &[T]) -> Option<T> {
147    a.first().cloned()
148}
149
150/// last(array)
151pub fn last<T: Clone>(a: &[T]) -> Option<T> {
152    a.last().cloned()
153}
154
155/// setElement(array, index, value) – panics if out of range except allowing
156/// index == len (append) in the original? JS code forbade index > length.
157/// If index == len we append.
158pub fn set_element<T: Clone>(a: &mut Vec<T>, index: isize, value: T) {
159    let len = isize::try_from(a.len()).expect("usize not fit in isize");
160    let mut idx = index;
161    if idx < 0 {
162        idx += len;
163    }
164    assert!(
165        !(idx < 0 || idx > len),
166        "Cannot insert at position {index} greater than appending to the length of the array."
167    );
168    if idx == len {
169        a.push(value);
170    } else {
171        a[usize::try_from(idx).expect("isize not fit in usize")] = value;
172    }
173}
174
175// ===============
176// SUBSET HELPERS (abSubset / anSubset / asSubset)
177// These treat the end as inclusive, and allow negative indexes.
178// ===============
179
180fn normalize_bounds(len: usize, start: isize, end: isize) -> (usize, usize) {
181    let l = isize::try_from(len).expect("usize not fit in isize");
182    let s = if start < 0 { l + start } else { start };
183    let e = if end < 0 { l + end } else { end };
184    let s = s.clamp(0, l.max(0));
185    let e = e.clamp(0, l.max(0));
186    (
187        usize::try_from(s).expect("isize not fit in usize"),
188        usize::try_from(e).expect("isize not fit in usize"),
189    )
190}
191
192pub fn slice_inclusive_bool(a: &[bool], start: isize, end: isize) -> Vec<bool> {
193    if a.is_empty() {
194        return Vec::new();
195    }
196    let (s, e) = normalize_bounds(a.len(), start, end);
197    if e < s {
198        return Vec::new();
199    }
200    a[s..=e.min(a.len() - 1)].to_vec()
201}
202
203pub fn slice_inclusive_i32(a: &[i32], start: isize, end: isize) -> Vec<i32> {
204    if a.is_empty() {
205        return Vec::new();
206    }
207    let (s, e) = normalize_bounds(a.len(), start, end);
208    if e < s {
209        return Vec::new();
210    }
211    a[s..=e.min(a.len() - 1)].to_vec()
212}
213
214pub fn slice_inclusive_string(
215    a: &[String],
216    start: isize,
217    end: isize,
218) -> Vec<String> {
219    if a.is_empty() {
220        return Vec::new();
221    }
222    let (s, e) = normalize_bounds(a.len(), start, end);
223    if e < s {
224        return Vec::new();
225    }
226    a[s..=e.min(a.len() - 1)].to_vec()
227}
228
229/// Convert a single primitive into a one-element array (abFromB / anFromN / asFromS).
230pub fn one_bool(b: bool) -> Vec<bool> {
231    vec![b]
232}
233pub fn one_i32(n: i32) -> Vec<i32> {
234    vec![n]
235}
236pub fn one_string(s: impl Into<String>) -> Vec<String> {
237    vec![s.into()]
238}
239
240/// contains(genericArrayIn, genericValue) – specialized to strings & ints for now.
241/// For a more dynamic approach we'd pass `Vec<Value>` and a Value.
242/// We implement a generic helper where T: `PartialEq`.
243pub fn contains<T: PartialEq>(a: &[T], needle: &T) -> bool {
244    a.iter().any(|v| v == needle)
245}
246
247pub fn index_of<T: PartialEq>(a: &[T], needle: &T) -> isize {
248    for (i, v) in a.iter().enumerate() {
249        if v == needle {
250            return isize::try_from(i).expect("usize not fit in isize");
251        }
252    }
253    -1
254}
255
256// ---------------
257// Printing arrays (space-delimited)
258// ---------------
259
260pub fn str_print_arr<T: ToString>(arr: &[T]) -> String {
261    arr.iter()
262        .map(std::string::ToString::to_string)
263        .collect::<Vec<_>>()
264        .join(" ")
265}
266
267/// Wrapper kept for compatibility: `print_array` -> `str_print_arr`
268pub fn print_array<T: ToString>(arr: &[T]) -> String {
269    str_print_arr(arr)
270}
271
272/// Wrapper kept for compatibility: `str_print_array` -> `str_print_arr`
273pub fn str_print_array<T: ToString>(arr: &[T]) -> String {
274    str_print_arr(arr)
275}
276
277/// Wrapper kept for compatibility: `print_arr` -> `str_print_arr`
278pub fn print_arr<T: ToString>(arr: &[T]) -> String {
279    str_print_arr(arr)
280}
281
282// ---------------
283// Summation
284// ---------------
285
286pub fn sum_array(a: &[i32]) -> i32 {
287    a.iter().copied().sum()
288}
289
290#[cfg(test)]
291mod tests {
292    use super::*;
293
294    #[crate::ctb_test]
295    fn test_slice_inclusive() {
296        let v = vec![1, 2, 3, 4, 5];
297        assert_eq!(slice_inclusive_i32(&v, 1, 3), vec![2, 3, 4]);
298        assert_eq!(slice_inclusive_i32(&v, -3, -1), vec![3, 4, 5]);
299        assert_eq!(slice_inclusive_i32(&v, 0, 0), vec![1]);
300    }
301
302    #[crate::ctb_test]
303    fn test_contains() {
304        let v = vec![String::from("a"), String::from("b")];
305        assert!(contains(&v, &"a".to_string()));
306        assert!(!contains(&v, &"c".to_string()));
307    }
308
309    // PART 2
310
311    #[crate::ctb_test]
312    fn test_index_of() {
313        let v = vec![1, 2, 3];
314        assert_eq!(index_of(&v, &2), 1);
315        assert_eq!(index_of(&v, &4), -1);
316    }
317
318    #[crate::ctb_test]
319    fn test_arr_eq() {
320        assert!(arr_eq(&[1, 2, 3], &[1, 2, 3]));
321        assert!(!arr_eq(&[1, 2], &[1, 2, 3]));
322    }
323
324    #[crate::ctb_test]
325    fn test_sum_array() {
326        assert_eq!(sum_array(&[1, 2, 3, 4]), 10);
327    }
328}