devela/code/util/
cfor.rs

1// devela::code::util::cfor
2//
3// Original source code by Joachim Enggård Nebel, licensed as MIT,
4// https://crates.io/crates/const_for/0.1.4
5//
6// WAIT: [for-loops in constants](https://github.com/rust-lang/rust/issues/87575)
7// IMPROVE: doesn't work in certain circumstances.
8
9/// A for loop that is usable in *compile-time* contexts.
10///
11/// It aims to work exactly like a normal for loop over a standard exclusive range,
12/// eg. `0..10` or `-5..5`. Unfortunately it doesn't support other types of ranges
13/// like `..10` or `2..=10`. So generally just use it like a regular for loop.
14///
15/// `.rev()` and `.step_by(x)` are implemented via macros instead of the
16/// non-const iter trait, and makes the loop behave as expected.
17///
18/// # Examples
19/// ```
20/// # use devela::cfor;
21/// let mut a = 0;
22/// cfor!(i in 0..5 => {
23///     a += i
24/// });
25/// assert!(a == 10)
26/// ```
27///
28/// This is equivalent to the following regular for loop, except it is usable in const context.
29/// ```
30/// let mut a = 0;
31/// for i in 0..5 {
32///     a += i
33/// }
34/// assert!(a == 10)
35/// ```
36///
37/// ## Custom step size
38/// A custom step size can be set:
39/// ```
40/// # use devela::cfor;
41/// let mut v = Vec::new();
42/// cfor!(i in (0..5).step_by(2) => {
43///     v.push(i)
44/// });
45/// assert!(v == vec![0, 2, 4])
46/// ```
47/// The loop behaves as if the function was called on the range,
48/// including requiring a usize, but it is implemented by a macro.
49///
50/// ## Reversed
51/// Iteration can be reversed:
52/// ```
53/// # use devela::cfor;
54/// let mut v = Vec::new();
55/// cfor!(i in (0..5).rev() => {
56///     v.push(i)
57/// });
58/// assert!(v == vec![4, 3, 2, 1, 0])
59/// ```
60/// The loop behaves as if the function was called on the range, but it is implemented by a macro.
61///
62/// ## Reversed and custom step size
63/// It is possible to combine rev and step_by, but each can only be appended once.
64/// So the following two examples are the only legal combinations.
65/// ```
66/// # use devela::cfor;
67/// // Reverse, then change step size
68/// let mut v = Vec::new();
69/// cfor!(i in (0..10).rev().step_by(4) => {
70///     v.push(i)
71/// });
72/// assert!(v == vec![9, 5, 1]);
73///
74/// // Change step size, then reverse
75/// let mut v = Vec::new();
76/// cfor!(i in (0..10).step_by(4).rev() => {
77///     v.push(i)
78/// });
79/// assert!(v == vec![8, 4, 0])
80/// ```
81///
82/// ## Notes
83/// You can use mutable and wildcard variables as the loop variable, and they act as expected.
84///
85/// ```
86/// // Mutable variable
87/// # use devela::cfor;
88/// let mut v = Vec::new();
89/// cfor!(mut i in (0..4) => {
90///     i *= 2;
91///     v.push(i)
92/// });
93/// assert!(v == vec![0, 2, 4, 6]);
94///
95/// // Wildcard variable
96/// let mut a = 0;
97/// cfor!(_ in 0..5 =>
98///    a += 1
99/// );
100/// assert!(a == 5)
101/// ```
102///
103/// The body of the loop can be any statement. This means that the following is legal,
104/// even though it is not in a regular for loop.
105/// ```
106/// # use devela::cfor;
107/// let mut a = 0;
108/// cfor!(_ in 0..5 => a += 1);
109///
110/// unsafe fn unsafe_function() {}
111/// cfor!(_ in 0..5 => unsafe {
112///    unsafe_function()
113/// });
114/// ```
115#[doc = crate::doc_!(vendor: "const_for")]
116#[macro_export]
117#[cfg_attr(cargo_primary_package, doc(hidden))]
118macro_rules! cfor {
119    ($var:pat_param in ($range:expr).step_by($step:expr) => $body:stmt) => {
120        {
121            let _: usize = $step;
122            let mut __ite = $range.start;
123            let __end = $range.end;
124            let mut __is_first = true;
125            let __step = $step;
126
127            loop {
128                if !__is_first {
129                    __ite += __step
130                }
131                __is_first = false;
132
133                let $var = __ite;
134
135                if __ite >= __end {
136                    break
137                }
138
139                $body
140            }
141        }
142    };
143
144    ($var:pat_param in ($range:expr).rev().step_by($step:expr) => $body:stmt) => {
145        {
146            let _: usize = $step;
147            let mut __ite = $range.end;
148            let __start = $range.start;
149            let mut __is_first = true;
150            let __step = $step;
151
152            loop {
153                if !__is_first {
154                    if __step + __start >= __ite {
155                        break
156                    }
157                    __ite -= __step
158                }
159                __is_first = false;
160
161                if __ite <= __start {
162                    break
163                }
164
165                // cannot underflow as __ite > __start
166                let $var = __ite - 1;
167
168                $body
169            }
170        }
171    };
172
173    ($var:pat_param in ($range:expr).rev() => $body:stmt) => {
174        cfor!($var in ($range).rev().step_by(1) => $body)
175    };
176
177    ($var:pat_param in ($range:expr).step_by($step:expr).rev() => $body:stmt) => {
178        cfor!($var in ($range.start..$range.end - ($range.end - $range.start - 1) % $step)
179            .rev().step_by($step) => $body)
180    };
181
182    ($var:pat_param in $range:expr => $body:stmt) => {
183        cfor!($var in ($range).step_by(1) => $body)
184    };
185}
186#[doc(inline)]
187pub use cfor;
188
189#[cfg(all(test, feature = "alloc"))]
190mod tests {
191    use super::cfor;
192    use crate::{vec_ as vec, Vec};
193
194    macro_rules! validate_loop {
195        (@impl $($loop:tt)*) => {
196            let mut c_values_hit = Vec::new();
197            cfor!(i in $($loop)* => {
198                c_values_hit.push(i);
199            });
200
201            let mut r_values_hit = Vec::new();
202            for i in $($loop)* {
203                r_values_hit.push(i);
204            };
205
206            assert!(c_values_hit == r_values_hit);
207        };
208
209        ($step: expr, $($loop:tt)*) => {
210            validate_loop!(@impl ($($loop)*).step_by(1));
211            validate_loop!(@impl ($($loop)*).step_by(1).rev());
212            validate_loop!(@impl ($($loop)*).rev().step_by(1));
213        };
214
215        ($($loop:tt)*) => {
216            validate_loop!(@impl $($loop)*);
217            validate_loop!(@impl ($($loop)*).rev());
218
219            validate_loop!(1, $($loop)*);
220            validate_loop!(2, $($loop)*);
221            validate_loop!(3, $($loop)*);
222            validate_loop!(8, $($loop)*);
223            validate_loop!(15, $($loop)*);
224            validate_loop!(17, $($loop)*);
225            validate_loop!(45, $($loop)*);
226            validate_loop!(150, $($loop)*);
227        };
228    }
229
230    #[test]
231    #[allow(unused_parens, reason = "(0..10)")]
232    fn equivalent_to_regular_for() {
233        validate_loop!(-10..10);
234        validate_loop!(0..10);
235        validate_loop!(-10..10);
236        validate_loop!((0..10));
237        validate_loop!(50..10);
238        validate_loop!(-15..-12);
239        validate_loop!(-14..0);
240        validate_loop!(-100..-50);
241        validate_loop!(-14..80);
242        validate_loop!(1..80);
243    }
244
245    #[test]
246    fn capture_range_at_beginning() {
247        let mut a = 113;
248        cfor!(i in 0..a-100 => {
249            a += i;
250        });
251        let mut b = 113;
252        for i in 0..b - 100 {
253            b += i;
254        }
255        assert_eq!(a, b);
256
257        let mut a = 0;
258        let mut step = 1;
259        cfor!(_ in (0..10).step_by(step) => {
260            a += step;
261            step += 1;
262        });
263        let mut b = 0;
264        let mut step = 1;
265        for _ in (0..10).step_by(step) {
266            b += step;
267            step += 1;
268        }
269        assert_eq!(a, b);
270    }
271
272    #[test]
273    const fn available_in_const() {
274        let mut a = 0;
275
276        cfor!(_ in 0..25 => {
277            a += 1
278        });
279        cfor!(_ in (0..25).rev() => {
280            a += 1
281        });
282        cfor!(_ in (0..100).step_by(2) =>
283            a += 1
284        );
285
286        cfor!(mut i in (0..3) => {
287            i += 1;
288            a += i
289        });
290
291        cfor!(_ in (0..7).rev() => {
292            a += 1
293        });
294
295        assert!(a == 25 + 25 + 50 + 6 + 7);
296    }
297
298    #[test]
299    fn no_underflow() {
300        cfor!(_ in (0u64..1).rev() => {});
301        let mut iterations: u64 = 0;
302        cfor!(_ in (i8::MIN..0).rev() => iterations += 1);
303        assert_eq!(iterations, 128);
304    }
305
306    #[test]
307    fn signed_can_go_negative() {
308        let mut actual = Vec::new();
309        cfor!(i in (-10..11).rev().step_by(5) => actual.push(i));
310        assert_eq!(actual, vec![10, 5, 0, -5, -10]);
311    }
312}