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}